mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 14:43:54 -05:00
feat(tools): google slides tool, terminal console virtualization, tool fixes (#2209)
* feat: google slides tool * fix oauth for slides, add remaining endpoints, update docs * optimize json dump viewer using react window * change slides to use google drive credentials * fix some tools * ack PR comments --------- Co-authored-by: waleed <walif6@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
This commit is contained in:
@@ -1084,6 +1084,27 @@ export function GoogleDocsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function GoogleSlidesIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 48 48'
|
||||
width='96px'
|
||||
height='96px'
|
||||
>
|
||||
<path
|
||||
fill='#FFC107'
|
||||
d='M37,45H11c-1.657,0-3-1.343-3-3V6c0-1.657,1.343-3,3-3h19l10,10v29C40,43.657,38.657,45,37,45z'
|
||||
/>
|
||||
<path fill='#FFECB3' d='M40 13L30 13 30 3z' />
|
||||
<path fill='#FFA000' d='M30 13L40 23 40 13z' />
|
||||
<path fill='#FFF8E1' d='M14 21H34V35H14z' />
|
||||
<path fill='#FFA000' d='M16 23H32V26H16zM16 28H28V30H16z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function GoogleCalendarIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
GoogleFormsIcon,
|
||||
GoogleIcon,
|
||||
GoogleSheetsIcon,
|
||||
GoogleSlidesIcon,
|
||||
GoogleVaultIcon,
|
||||
GrafanaIcon,
|
||||
HubspotIcon,
|
||||
@@ -192,6 +193,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
hubspot: HubspotIcon,
|
||||
grafana: GrafanaIcon,
|
||||
google_vault: GoogleVaultIcon,
|
||||
google_slides: GoogleSlidesIcon,
|
||||
google_sheets: GoogleSheetsIcon,
|
||||
google_forms: GoogleFormsIcon,
|
||||
google_drive: GoogleDriveIcon,
|
||||
|
||||
@@ -149,6 +149,7 @@ Get the top pages of a target domain sorted by organic traffic. Returns page URL
|
||||
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
|
||||
| `limit` | number | No | Maximum number of results to return \(default: 100\) |
|
||||
| `offset` | number | No | Number of results to skip for pagination |
|
||||
| `select` | string | No | Comma-separated list of fields to return \(e.g., url,traffic,keywords,top_keyword,value\). Default: url,traffic,keywords,top_keyword,value |
|
||||
| `apiKey` | string | Yes | Ahrefs API Key |
|
||||
|
||||
#### Output
|
||||
|
||||
185
apps/docs/content/docs/en/tools/google_slides.mdx
Normal file
185
apps/docs/content/docs/en/tools/google_slides.mdx
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
title: Google Slides
|
||||
description: Read, write, and create presentations
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="google_slides"
|
||||
color="#E0E0E0"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Google Slides](https://slides.google.com) is a dynamic cloud-based presentation application that allows users to create, edit, collaborate on, and present slideshows in real-time. As part of Google's productivity suite, Google Slides offers a flexible platform for designing engaging presentations, collaborating with others, and sharing content seamlessly through the cloud.
|
||||
|
||||
Learn how to integrate the Google Slides tools in Sim to effortlessly manage presentations as part of your automated workflows. With Sim, you can read, write, create, and update Google Slides presentations directly through your agents and automated processes, making it easy to deliver up-to-date information, generate custom reports, or produce branded decks programmatically.
|
||||
|
||||
With Google Slides, you can:
|
||||
|
||||
- **Create and edit presentations**: Design visually appealing slides with themes, layouts, and multimedia content
|
||||
- **Collaborate in real-time**: Work simultaneously with teammates, comment, assign tasks, and receive live feedback on presentations
|
||||
- **Present anywhere**: Display presentations online or offline, share links, or publish to the web
|
||||
- **Add images and rich content**: Insert images, graphics, charts, and videos to make your presentations engaging
|
||||
- **Integrate with other services**: Connect seamlessly with Google Drive, Docs, Sheets, and other third-party tools
|
||||
- **Access from any device**: Use Google Slides on desktops, laptops, tablets, and mobile devices for maximum flexibility
|
||||
|
||||
In Sim, the Google Slides integration enables your agents to interact directly with presentation files programmatically. Automate tasks like reading slide content, inserting new slides or images, replacing text throughout a deck, generating new presentations, and retrieving slide thumbnails. This empowers you to scale content creation, keep presentations up-to-date, and embed them into automated document workflows. By connecting Sim with Google Slides, you facilitate AI-driven presentation management—making it easy to generate, update, or extract information from presentations without manual effort.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Google Slides into the workflow. Can read, write, create presentations, replace text, add slides, add images, and get thumbnails.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `google_slides_read`
|
||||
|
||||
Read content from a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation to read |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slides` | json | Array of slides with their content |
|
||||
| `metadata` | json | Presentation metadata including ID, title, and URL |
|
||||
|
||||
### `google_slides_write`
|
||||
|
||||
Write or update content in a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation to write to |
|
||||
| `content` | string | Yes | The content to write to the slide |
|
||||
| `slideIndex` | number | No | The index of the slide to write to \(defaults to first slide\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `updatedContent` | boolean | Indicates if presentation content was updated successfully |
|
||||
| `metadata` | json | Updated presentation metadata including ID, title, and URL |
|
||||
|
||||
### `google_slides_create`
|
||||
|
||||
Create a new Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | Yes | The title of the presentation to create |
|
||||
| `content` | string | No | The content to add to the first slide |
|
||||
| `folderSelector` | string | No | Select the folder to create the presentation in |
|
||||
| `folderId` | string | No | The ID of the folder to create the presentation in \(internal use\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `metadata` | json | Created presentation metadata including ID, title, and URL |
|
||||
|
||||
### `google_slides_replace_all_text`
|
||||
|
||||
Find and replace all occurrences of text throughout a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation |
|
||||
| `findText` | string | Yes | The text to find \(e.g., \{\{placeholder\}\}\) |
|
||||
| `replaceText` | string | Yes | The text to replace with |
|
||||
| `matchCase` | boolean | No | Whether the search should be case-sensitive \(default: true\) |
|
||||
| `pageObjectIds` | string | No | Comma-separated list of slide object IDs to limit replacements to specific slides \(leave empty for all slides\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `occurrencesChanged` | number | Number of text occurrences that were replaced |
|
||||
| `metadata` | json | Operation metadata including presentation ID and URL |
|
||||
|
||||
### `google_slides_add_slide`
|
||||
|
||||
Add a new slide to a Google Slides presentation with a specified layout
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation |
|
||||
| `layout` | string | No | The predefined layout for the slide \(BLANK, TITLE, TITLE_AND_BODY, TITLE_ONLY, SECTION_HEADER, etc.\). Defaults to BLANK. |
|
||||
| `insertionIndex` | number | No | The optional zero-based index indicating where to insert the slide. If not specified, the slide is added at the end. |
|
||||
| `placeholderIdMappings` | string | No | JSON array of placeholder mappings to assign custom object IDs to placeholders. Format: \[\{"layoutPlaceholder":\{"type":"TITLE"\},"objectId":"custom_title_id"\}\] |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slideId` | string | The object ID of the newly created slide |
|
||||
| `metadata` | json | Operation metadata including presentation ID, layout, and URL |
|
||||
|
||||
### `google_slides_add_image`
|
||||
|
||||
Insert an image into a specific slide in a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation |
|
||||
| `pageObjectId` | string | Yes | The object ID of the slide/page to add the image to |
|
||||
| `imageUrl` | string | Yes | The publicly accessible URL of the image \(must be PNG, JPEG, or GIF, max 50MB\) |
|
||||
| `width` | number | No | Width of the image in points \(default: 300\) |
|
||||
| `height` | number | No | Height of the image in points \(default: 200\) |
|
||||
| `positionX` | number | No | X position from the left edge in points \(default: 100\) |
|
||||
| `positionY` | number | No | Y position from the top edge in points \(default: 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `imageId` | string | The object ID of the newly created image |
|
||||
| `metadata` | json | Operation metadata including presentation ID and image URL |
|
||||
|
||||
### `google_slides_get_thumbnail`
|
||||
|
||||
Generate a thumbnail image of a specific slide in a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation |
|
||||
| `pageObjectId` | string | Yes | The object ID of the slide/page to get a thumbnail for |
|
||||
| `thumbnailSize` | string | No | The size of the thumbnail: SMALL \(200px\), MEDIUM \(800px\), or LARGE \(1600px\). Defaults to MEDIUM. |
|
||||
| `mimeType` | string | No | The MIME type of the thumbnail image: PNG or GIF. Defaults to PNG. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `contentUrl` | string | URL to the thumbnail image \(valid for 30 minutes\) |
|
||||
| `width` | number | Width of the thumbnail in pixels |
|
||||
| `height` | number | Height of the thumbnail in pixels |
|
||||
| `metadata` | json | Operation metadata including presentation ID and page object ID |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `google_slides`
|
||||
@@ -364,6 +364,8 @@ Update an existing schedule in incident.io
|
||||
| `id` | string | Yes | The ID of the schedule to update |
|
||||
| `name` | string | No | New name for the schedule |
|
||||
| `timezone` | string | No | New timezone for the schedule \(e.g., America/New_York\) |
|
||||
| `config` | string | No | Schedule configuration as JSON string with rotations. Example: \{"rotations": \[\{"name": "Primary", "users": \[\{"id": "user_id"\}\], "handover_start_at": "2024-01-01T09:00:00Z", "handovers": \[\{"interval": 1, "interval_type": "weekly"\}\]\}\]\} |
|
||||
| `Example` | string | No | No description |
|
||||
|
||||
#### Output
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ Create or update a company in Intercom
|
||||
| `plan` | string | No | The company plan name |
|
||||
| `size` | number | No | The number of employees in the company |
|
||||
| `industry` | string | No | The industry the company operates in |
|
||||
| `monthly_spend` | number | No | How much revenue the company generates for your business |
|
||||
| `monthly_spend` | number | No | How much revenue the company generates for your business. Note: This field truncates floats to whole integers \(e.g., 155.98 becomes 155\) |
|
||||
| `custom_attributes` | string | No | Custom attributes as JSON object |
|
||||
|
||||
#### Output
|
||||
@@ -199,7 +199,7 @@ Retrieve a single company by ID from Intercom
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
List all companies from Intercom with pagination support
|
||||
List all companies from Intercom with pagination support. Note: This endpoint has a limit of 10,000 companies that can be returned using pagination. For datasets larger than 10,000 companies, use the Scroll API instead.
|
||||
|
||||
#### Input
|
||||
|
||||
@@ -262,7 +262,7 @@ Reply to a conversation as an admin in Intercom
|
||||
| `conversationId` | string | Yes | Conversation ID to reply to |
|
||||
| `message_type` | string | Yes | Message type: "comment" or "note" |
|
||||
| `body` | string | Yes | The text body of the reply |
|
||||
| `admin_id` | string | Yes | The ID of the admin authoring the reply |
|
||||
| `admin_id` | string | No | The ID of the admin authoring the reply. If not provided, a default admin \(Operator/Fin\) will be used. |
|
||||
| `attachment_urls` | string | No | Comma-separated list of image URLs \(max 10\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Kalshi
|
||||
description: Access prediction markets data from Kalshi
|
||||
description: Access prediction markets and trade on Kalshi
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -28,7 +28,7 @@ By using these unified tools and endpoints, you can seamlessly incorporate Kalsh
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, and exchange status.
|
||||
Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, exchange status, and place/cancel/amend trades.
|
||||
|
||||
|
||||
|
||||
@@ -175,16 +175,34 @@ Retrieve your orders from Kalshi with optional filtering
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Orders data and metadata |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
Retrieve details of a specific order by ID from Kalshi
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Yes | Your Kalshi API Key ID |
|
||||
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
|
||||
| `orderId` | string | Yes | The order ID to retrieve |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Order data |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
Retrieve the orderbook (bids and asks) for a specific market
|
||||
Retrieve the orderbook (yes and no bids) for a specific market
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | Yes | Market ticker \(e.g., KXBTC-24DEC31\) |
|
||||
| `depth` | number | No | Number of price levels to return per side |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -195,15 +213,12 @@ Retrieve the orderbook (bids and asks) for a specific market
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
Retrieve recent trades across all markets or for a specific market
|
||||
Retrieve recent trades across all markets
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | No | Filter by market ticker |
|
||||
| `minTs` | number | No | Minimum timestamp \(Unix milliseconds\) |
|
||||
| `maxTs` | number | No | Maximum timestamp \(Unix milliseconds\) |
|
||||
| `limit` | string | No | Number of results \(1-1000, default: 100\) |
|
||||
| `cursor` | string | No | Pagination cursor for next page |
|
||||
|
||||
@@ -224,9 +239,9 @@ Retrieve OHLC candlestick data for a specific market
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `seriesTicker` | string | Yes | Series ticker |
|
||||
| `ticker` | string | Yes | Market ticker \(e.g., KXBTC-24DEC31\) |
|
||||
| `startTs` | number | No | Start timestamp \(Unix milliseconds\) |
|
||||
| `endTs` | number | No | End timestamp \(Unix milliseconds\) |
|
||||
| `periodInterval` | number | No | Period interval: 1 \(1min\), 60 \(1hour\), or 1440 \(1day\) |
|
||||
| `startTs` | number | Yes | Start timestamp \(Unix seconds\) |
|
||||
| `endTs` | number | Yes | End timestamp \(Unix seconds\) |
|
||||
| `periodInterval` | number | Yes | Period interval: 1 \(1min\), 60 \(1hour\), or 1440 \(1day\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -292,6 +307,89 @@ Retrieve the current status of the Kalshi exchange (trading and exchange activit
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Exchange status data and metadata |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
Create a new order on a Kalshi prediction market
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Yes | Your Kalshi API Key ID |
|
||||
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
|
||||
| `ticker` | string | Yes | Market ticker \(e.g., KXBTC-24DEC31\) |
|
||||
| `side` | string | Yes | Side of the order: 'yes' or 'no' |
|
||||
| `action` | string | Yes | Action type: 'buy' or 'sell' |
|
||||
| `count` | string | Yes | Number of contracts \(minimum 1\) |
|
||||
| `type` | string | No | Order type: 'limit' or 'market' \(default: limit\) |
|
||||
| `yesPrice` | string | No | Yes price in cents \(1-99\) |
|
||||
| `noPrice` | string | No | No price in cents \(1-99\) |
|
||||
| `yesPriceDollars` | string | No | Yes price in dollars \(e.g., "0.56"\) |
|
||||
| `noPriceDollars` | string | No | No price in dollars \(e.g., "0.56"\) |
|
||||
| `clientOrderId` | string | No | Custom order identifier |
|
||||
| `expirationTs` | string | No | Unix timestamp for order expiration |
|
||||
| `timeInForce` | string | No | Time in force: 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel' |
|
||||
| `buyMaxCost` | string | No | Maximum cost in cents \(auto-enables fill_or_kill\) |
|
||||
| `postOnly` | string | No | Set to 'true' for maker-only orders |
|
||||
| `reduceOnly` | string | No | Set to 'true' for position reduction only |
|
||||
| `selfTradePreventionType` | string | No | Self-trade prevention: 'taker_at_cross' or 'maker' |
|
||||
| `orderGroupId` | string | No | Associated order group ID |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created order data |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
Cancel an existing order on Kalshi
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Yes | Your Kalshi API Key ID |
|
||||
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
|
||||
| `orderId` | string | Yes | The order ID to cancel |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Canceled order data |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
Modify the price or quantity of an existing order on Kalshi
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Yes | Your Kalshi API Key ID |
|
||||
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
|
||||
| `orderId` | string | Yes | The order ID to amend |
|
||||
| `ticker` | string | Yes | Market ticker |
|
||||
| `side` | string | Yes | Side of the order: 'yes' or 'no' |
|
||||
| `action` | string | Yes | Action type: 'buy' or 'sell' |
|
||||
| `clientOrderId` | string | Yes | The original client-specified order ID |
|
||||
| `updatedClientOrderId` | string | Yes | The new client-specified order ID after amendment |
|
||||
| `count` | string | No | Updated quantity for the order |
|
||||
| `yesPrice` | string | No | Updated yes price in cents \(1-99\) |
|
||||
| `noPrice` | string | No | Updated no price in cents \(1-99\) |
|
||||
| `yesPriceDollars` | string | No | Updated yes price in dollars \(e.g., "0.56"\) |
|
||||
| `noPriceDollars` | string | No | Updated no price in dollars \(e.g., "0.56"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Amended order data |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"google_forms",
|
||||
"google_search",
|
||||
"google_sheets",
|
||||
"google_slides",
|
||||
"google_vault",
|
||||
"grafana",
|
||||
"hubspot",
|
||||
|
||||
@@ -44,7 +44,7 @@ Retrieve a list of prediction markets from Polymarket with optional filtering
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | No | Filter by closed status \(true/false\). Use false for active markets only. |
|
||||
| `order` | string | No | Sort field \(e.g., id, volume, liquidity\) |
|
||||
| `order` | string | No | Sort field \(e.g., volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | No | Sort direction \(true for ascending, false for descending\) |
|
||||
| `tagId` | string | No | Filter by tag ID |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
@@ -84,7 +84,7 @@ Retrieve a list of events from Polymarket with optional filtering
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | No | Filter by closed status \(true/false\). Use false for active events only. |
|
||||
| `order` | string | No | Sort field \(e.g., id, volume\) |
|
||||
| `order` | string | No | Sort field \(e.g., volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | No | Sort direction \(true for ascending, false for descending\) |
|
||||
| `tagId` | string | No | Filter by tag ID |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
|
||||
@@ -1,40 +1,21 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTerminalStore } from '@/stores/terminal'
|
||||
|
||||
/**
|
||||
* Constants for output panel sizing
|
||||
* Must match MIN_OUTPUT_PANEL_WIDTH_PX and BLOCK_COLUMN_WIDTH_PX in terminal.tsx
|
||||
*/
|
||||
const MIN_WIDTH = 440
|
||||
const BLOCK_COLUMN_WIDTH = 240
|
||||
|
||||
/**
|
||||
* Custom hook to handle output panel horizontal resize functionality.
|
||||
* Manages mouse events for resizing and enforces min/max width constraints.
|
||||
*
|
||||
* @returns Resize state and handlers
|
||||
*/
|
||||
export function useOutputPanelResize() {
|
||||
const { setOutputPanelWidth } = useTerminalStore()
|
||||
const setOutputPanelWidth = useTerminalStore((state) => state.setOutputPanelWidth)
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
|
||||
/**
|
||||
* Handles mouse down on resize handle
|
||||
*/
|
||||
const handleMouseDown = useCallback(() => {
|
||||
setIsResizing(true)
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Setup resize event listeners and body styles when resizing.
|
||||
* Cleanup is handled automatically by the effect's return function.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isResizing) return
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
// Calculate width from the right edge of the viewport
|
||||
// Account for panel width on the right side
|
||||
const panelWidth = Number.parseInt(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--panel-width') || '0'
|
||||
)
|
||||
@@ -43,13 +24,10 @@ export function useOutputPanelResize() {
|
||||
)
|
||||
|
||||
const newWidth = window.innerWidth - e.clientX - panelWidth
|
||||
|
||||
// Calculate max width: total terminal width minus block column width
|
||||
const terminalWidth = window.innerWidth - sidebarWidth - panelWidth
|
||||
const maxWidth = terminalWidth - BLOCK_COLUMN_WIDTH
|
||||
|
||||
// Clamp between min and max width
|
||||
const clampedWidth = Math.max(MIN_WIDTH, Math.min(newWidth, maxWidth))
|
||||
|
||||
setOutputPanelWidth(clampedWidth)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,22 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useTerminalStore } from '@/stores/terminal'
|
||||
|
||||
/**
|
||||
* Constants for terminal sizing
|
||||
*/
|
||||
const MIN_HEIGHT = 30
|
||||
const MAX_HEIGHT_PERCENTAGE = 0.7 // 70% of viewport height
|
||||
const MAX_HEIGHT_PERCENTAGE = 0.7
|
||||
|
||||
/**
|
||||
* Custom hook to handle terminal resize functionality.
|
||||
* Manages mouse events for resizing and enforces min/max height constraints.
|
||||
* Maximum height is capped at 70% of the viewport height for optimal layout.
|
||||
*
|
||||
* @returns Resize state and handlers
|
||||
*/
|
||||
export function useTerminalResize() {
|
||||
const { setTerminalHeight, isResizing, setIsResizing } = useTerminalStore()
|
||||
const setTerminalHeight = useTerminalStore((state) => state.setTerminalHeight)
|
||||
const isResizing = useTerminalStore((state) => state.isResizing)
|
||||
const setIsResizing = useTerminalStore((state) => state.setIsResizing)
|
||||
|
||||
/**
|
||||
* Handles mouse down on resize handle
|
||||
*/
|
||||
const handleMouseDown = useCallback(() => {
|
||||
setIsResizing(true)
|
||||
}, [setIsResizing])
|
||||
|
||||
/**
|
||||
* Setup resize event listeners and body styles when resizing
|
||||
* Cleanup is handled automatically by the effect's return function
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isResizing) return
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
// Calculate height from the bottom edge of the viewport
|
||||
const newHeight = window.innerHeight - e.clientY
|
||||
const maxHeight = window.innerHeight * MAX_HEIGHT_PERCENTAGE
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
ArrowDown,
|
||||
@@ -18,9 +18,9 @@ import {
|
||||
Trash2,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import {
|
||||
Button,
|
||||
Code,
|
||||
Input,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
PopoverScrollArea,
|
||||
PopoverTrigger,
|
||||
Tooltip,
|
||||
VirtualizedCodeViewer,
|
||||
} from '@/components/emcn'
|
||||
import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
|
||||
import { createCommands } from '@/app/workspace/[workspaceId]/utils/commands-utils'
|
||||
@@ -237,6 +238,42 @@ const isEventFromEditableElement = (e: KeyboardEvent): boolean => {
|
||||
return false
|
||||
}
|
||||
|
||||
interface OutputCodeContentProps {
|
||||
code: string
|
||||
language: 'javascript' | 'json'
|
||||
wrapText: boolean
|
||||
searchQuery: string | undefined
|
||||
currentMatchIndex: number
|
||||
onMatchCountChange: (count: number) => void
|
||||
contentRef: React.RefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
const OutputCodeContent = React.memo(function OutputCodeContent({
|
||||
code,
|
||||
language,
|
||||
wrapText,
|
||||
searchQuery,
|
||||
currentMatchIndex,
|
||||
onMatchCountChange,
|
||||
contentRef,
|
||||
}: OutputCodeContentProps) {
|
||||
return (
|
||||
<VirtualizedCodeViewer
|
||||
code={code}
|
||||
showGutter
|
||||
language={language}
|
||||
className='m-0 min-h-full rounded-none border-0 bg-[var(--surface-1)]'
|
||||
paddingLeft={8}
|
||||
gutterStyle={{ backgroundColor: 'transparent' }}
|
||||
wrapText={wrapText}
|
||||
searchQuery={searchQuery}
|
||||
currentMatchIndex={currentMatchIndex}
|
||||
onMatchCountChange={onMatchCountChange}
|
||||
contentRef={contentRef}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Terminal component with resizable height that persists across page refreshes.
|
||||
*
|
||||
@@ -254,7 +291,6 @@ export function Terminal() {
|
||||
const prevEntriesLengthRef = useRef(0)
|
||||
const prevWorkflowEntriesLengthRef = useRef(0)
|
||||
const {
|
||||
terminalHeight,
|
||||
setTerminalHeight,
|
||||
lastExpandedHeight,
|
||||
outputPanelWidth,
|
||||
@@ -263,10 +299,16 @@ export function Terminal() {
|
||||
setOpenOnRun,
|
||||
setHasHydrated,
|
||||
} = useTerminalStore()
|
||||
const entries = useTerminalConsoleStore((state) => state.entries)
|
||||
const isExpanded = useTerminalStore((state) => state.terminalHeight > NEAR_MIN_THRESHOLD)
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
const workflowEntriesSelector = useCallback(
|
||||
(state: { entries: ConsoleEntry[] }) =>
|
||||
state.entries.filter((entry) => entry.workflowId === activeWorkflowId),
|
||||
[activeWorkflowId]
|
||||
)
|
||||
const entries = useTerminalConsoleStore(useShallow(workflowEntriesSelector))
|
||||
const clearWorkflowConsole = useTerminalConsoleStore((state) => state.clearWorkflowConsole)
|
||||
const exportConsoleCSV = useTerminalConsoleStore((state) => state.exportConsoleCSV)
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
const [selectedEntry, setSelectedEntry] = useState<ConsoleEntry | null>(null)
|
||||
const [isToggling, setIsToggling] = useState(false)
|
||||
const [wrapText, setWrapText] = useState(true)
|
||||
@@ -304,8 +346,6 @@ export function Terminal() {
|
||||
hasActiveFilters,
|
||||
} = useTerminalFilters()
|
||||
|
||||
const isExpanded = terminalHeight > NEAR_MIN_THRESHOLD
|
||||
|
||||
/**
|
||||
* Expands the terminal to its last meaningful height, with safeguards:
|
||||
* - Never expands below {@link DEFAULT_EXPANDED_HEIGHT}.
|
||||
@@ -322,13 +362,7 @@ export function Terminal() {
|
||||
setTerminalHeight(targetHeight)
|
||||
}, [lastExpandedHeight, setTerminalHeight])
|
||||
|
||||
/**
|
||||
* Get all entries for current workflow (before filtering) for filter options
|
||||
*/
|
||||
const allWorkflowEntries = useMemo(() => {
|
||||
if (!activeWorkflowId) return []
|
||||
return entries.filter((entry) => entry.workflowId === activeWorkflowId)
|
||||
}, [entries, activeWorkflowId])
|
||||
const allWorkflowEntries = entries
|
||||
|
||||
/**
|
||||
* Filter entries for current workflow and apply filters
|
||||
@@ -425,6 +459,11 @@ export function Terminal() {
|
||||
return selectedEntry.output
|
||||
}, [selectedEntry, showInput])
|
||||
|
||||
const outputDataStringified = useMemo(() => {
|
||||
if (outputData === null || outputData === undefined) return ''
|
||||
return JSON.stringify(outputData, null, 2)
|
||||
}, [outputData])
|
||||
|
||||
/**
|
||||
* Auto-open the terminal on new entries when "Open on run" is enabled.
|
||||
* This mirrors the header toggle behavior by using expandToLastHeight,
|
||||
@@ -439,13 +478,12 @@ export function Terminal() {
|
||||
const previousLength = prevWorkflowEntriesLengthRef.current
|
||||
const currentLength = allWorkflowEntries.length
|
||||
|
||||
// Only react when new entries are added for the active workflow
|
||||
if (currentLength > previousLength && terminalHeight <= MIN_HEIGHT) {
|
||||
if (currentLength > previousLength && !isExpanded) {
|
||||
expandToLastHeight()
|
||||
}
|
||||
|
||||
prevWorkflowEntriesLengthRef.current = currentLength
|
||||
}, [allWorkflowEntries.length, expandToLastHeight, openOnRun, terminalHeight])
|
||||
}, [allWorkflowEntries.length, expandToLastHeight, openOnRun, isExpanded])
|
||||
|
||||
/**
|
||||
* Handle row click - toggle if clicking same entry
|
||||
@@ -485,13 +523,11 @@ export function Terminal() {
|
||||
const handleCopy = useCallback(() => {
|
||||
if (!selectedEntry) return
|
||||
|
||||
const textToCopy = shouldShowCodeDisplay
|
||||
? selectedEntry.input.code
|
||||
: JSON.stringify(outputData, null, 2)
|
||||
const textToCopy = shouldShowCodeDisplay ? selectedEntry.input.code : outputDataStringified
|
||||
|
||||
navigator.clipboard.writeText(textToCopy)
|
||||
setShowCopySuccess(true)
|
||||
}, [selectedEntry, outputData, shouldShowCodeDisplay])
|
||||
}, [selectedEntry, outputDataStringified, shouldShowCodeDisplay])
|
||||
|
||||
/**
|
||||
* Clears the console for the active workflow.
|
||||
@@ -542,7 +578,7 @@ export function Terminal() {
|
||||
}, [matchCount])
|
||||
|
||||
/**
|
||||
* Handles match count change from Code.Viewer.
|
||||
* Handles match count change from VirtualizedCodeViewer.
|
||||
*/
|
||||
const handleMatchCountChange = useCallback((count: number) => {
|
||||
setMatchCount(count)
|
||||
@@ -1576,15 +1612,11 @@ export function Terminal() {
|
||||
{/* Content */}
|
||||
<div className='flex-1 overflow-x-auto overflow-y-auto'>
|
||||
{shouldShowCodeDisplay ? (
|
||||
<Code.Viewer
|
||||
<OutputCodeContent
|
||||
code={selectedEntry.input.code}
|
||||
showGutter
|
||||
language={
|
||||
(selectedEntry.input.language as 'javascript' | 'json') || 'javascript'
|
||||
}
|
||||
className='m-0 min-h-full rounded-none border-0 bg-[var(--surface-1)]'
|
||||
paddingLeft={8}
|
||||
gutterStyle={{ backgroundColor: 'transparent' }}
|
||||
wrapText={wrapText}
|
||||
searchQuery={isOutputSearchActive ? outputSearchQuery : undefined}
|
||||
currentMatchIndex={currentMatchIndex}
|
||||
@@ -1592,13 +1624,9 @@ export function Terminal() {
|
||||
contentRef={outputContentRef}
|
||||
/>
|
||||
) : (
|
||||
<Code.Viewer
|
||||
code={JSON.stringify(outputData, null, 2)}
|
||||
showGutter
|
||||
<OutputCodeContent
|
||||
code={outputDataStringified}
|
||||
language='json'
|
||||
className='m-0 min-h-full rounded-none border-0 bg-[var(--surface-1)]'
|
||||
paddingLeft={8}
|
||||
gutterStyle={{ backgroundColor: 'transparent' }}
|
||||
wrapText={wrapText}
|
||||
searchQuery={isOutputSearchActive ? outputSearchQuery : undefined}
|
||||
currentMatchIndex={currentMatchIndex}
|
||||
|
||||
433
apps/sim/blocks/blocks/google_slides.ts
Normal file
433
apps/sim/blocks/blocks/google_slides.ts
Normal file
@@ -0,0 +1,433 @@
|
||||
import { GoogleSlidesIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { GoogleSlidesResponse } from '@/tools/google_slides/types'
|
||||
|
||||
export const GoogleSlidesBlock: BlockConfig<GoogleSlidesResponse> = {
|
||||
type: 'google_slides',
|
||||
name: 'Google Slides',
|
||||
description: 'Read, write, and create presentations',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrate Google Slides into the workflow. Can read, write, create presentations, replace text, add slides, add images, and get thumbnails.',
|
||||
docsLink: 'https://docs.sim.ai/tools/google_slides',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: GoogleSlidesIcon,
|
||||
subBlocks: [
|
||||
// Operation selector
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Read Presentation', id: 'read' },
|
||||
{ label: 'Write to Presentation', id: 'write' },
|
||||
{ label: 'Create Presentation', id: 'create' },
|
||||
{ label: 'Replace All Text', id: 'replace_all_text' },
|
||||
{ label: 'Add Slide', id: 'add_slide' },
|
||||
{ label: 'Add Image', id: 'add_image' },
|
||||
{ label: 'Get Thumbnail', id: 'get_thumbnail' },
|
||||
],
|
||||
value: () => 'read',
|
||||
},
|
||||
// Google Slides Credentials
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Google Account',
|
||||
type: 'oauth-input',
|
||||
required: true,
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
],
|
||||
placeholder: 'Select Google account',
|
||||
},
|
||||
// Presentation selector (basic mode) - for operations that need an existing presentation
|
||||
{
|
||||
id: 'presentationId',
|
||||
title: 'Select Presentation',
|
||||
type: 'file-selector',
|
||||
canonicalParamId: 'presentationId',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [],
|
||||
mimeType: 'application/vnd.google-apps.presentation',
|
||||
placeholder: 'Select a presentation',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'basic',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['read', 'write', 'replace_all_text', 'add_slide', 'add_image', 'get_thumbnail'],
|
||||
},
|
||||
},
|
||||
// Manual presentation ID input (advanced mode)
|
||||
{
|
||||
id: 'manualPresentationId',
|
||||
title: 'Presentation ID',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'presentationId',
|
||||
placeholder: 'Enter presentation ID',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['read', 'write', 'replace_all_text', 'add_slide', 'add_image', 'get_thumbnail'],
|
||||
},
|
||||
},
|
||||
|
||||
// ========== Write Operation Fields ==========
|
||||
{
|
||||
id: 'slideIndex',
|
||||
title: 'Slide Index',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter slide index (0 for first slide)',
|
||||
condition: { field: 'operation', value: 'write' },
|
||||
},
|
||||
{
|
||||
id: 'content',
|
||||
title: 'Content',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter slide content',
|
||||
condition: { field: 'operation', value: 'write' },
|
||||
required: true,
|
||||
},
|
||||
|
||||
// ========== Create Operation Fields ==========
|
||||
{
|
||||
id: 'title',
|
||||
title: 'Presentation Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter title for the new presentation',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
required: true,
|
||||
},
|
||||
// Folder selector (basic mode)
|
||||
{
|
||||
id: 'folderSelector',
|
||||
title: 'Select Parent Folder',
|
||||
type: 'file-selector',
|
||||
canonicalParamId: 'folderId',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [],
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
placeholder: 'Select a parent folder',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'basic',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
},
|
||||
// Manual folder ID input (advanced mode)
|
||||
{
|
||||
id: 'folderId',
|
||||
title: 'Parent Folder ID',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'folderId',
|
||||
placeholder: 'Enter parent folder ID (leave empty for root folder)',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
},
|
||||
// Content Field for create operation
|
||||
{
|
||||
id: 'createContent',
|
||||
title: 'Initial Content',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter initial slide content (optional)',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
},
|
||||
|
||||
// ========== Replace All Text Operation Fields ==========
|
||||
{
|
||||
id: 'findText',
|
||||
title: 'Find Text',
|
||||
type: 'short-input',
|
||||
placeholder: 'Text to find (e.g., {{placeholder}})',
|
||||
condition: { field: 'operation', value: 'replace_all_text' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'replaceText',
|
||||
title: 'Replace With',
|
||||
type: 'short-input',
|
||||
placeholder: 'Text to replace with',
|
||||
condition: { field: 'operation', value: 'replace_all_text' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'matchCase',
|
||||
title: 'Match Case',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'replace_all_text' },
|
||||
},
|
||||
{
|
||||
id: 'pageObjectIds',
|
||||
title: 'Limit to Slides (IDs)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated slide IDs (leave empty for all)',
|
||||
condition: { field: 'operation', value: 'replace_all_text' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// ========== Add Slide Operation Fields ==========
|
||||
{
|
||||
id: 'layout',
|
||||
title: 'Slide Layout',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Blank', id: 'BLANK' },
|
||||
{ label: 'Title', id: 'TITLE' },
|
||||
{ label: 'Title and Body', id: 'TITLE_AND_BODY' },
|
||||
{ label: 'Title Only', id: 'TITLE_ONLY' },
|
||||
{ label: 'Title and Two Columns', id: 'TITLE_AND_TWO_COLUMNS' },
|
||||
{ label: 'Section Header', id: 'SECTION_HEADER' },
|
||||
{ label: 'Caption Only', id: 'CAPTION_ONLY' },
|
||||
{ label: 'Main Point', id: 'MAIN_POINT' },
|
||||
{ label: 'Big Number', id: 'BIG_NUMBER' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'add_slide' },
|
||||
value: () => 'BLANK',
|
||||
},
|
||||
{
|
||||
id: 'insertionIndex',
|
||||
title: 'Insertion Position',
|
||||
type: 'short-input',
|
||||
placeholder: 'Position to insert slide (leave empty for end)',
|
||||
condition: { field: 'operation', value: 'add_slide' },
|
||||
},
|
||||
{
|
||||
id: 'placeholderIdMappings',
|
||||
title: 'Placeholder ID Mappings',
|
||||
type: 'long-input',
|
||||
placeholder: 'JSON array: [{"layoutPlaceholder":{"type":"TITLE"},"objectId":"my_title"}]',
|
||||
condition: { field: 'operation', value: 'add_slide' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// ========== Add Image Operation Fields ==========
|
||||
{
|
||||
id: 'pageObjectId',
|
||||
title: 'Slide ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Object ID of the slide to add image to',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'imageUrl',
|
||||
title: 'Image URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'Public URL of the image (PNG, JPEG, or GIF)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'imageWidth',
|
||||
title: 'Width (points)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Image width in points (default: 300)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
},
|
||||
{
|
||||
id: 'imageHeight',
|
||||
title: 'Height (points)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Image height in points (default: 200)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
},
|
||||
{
|
||||
id: 'positionX',
|
||||
title: 'X Position (points)',
|
||||
type: 'short-input',
|
||||
placeholder: 'X position from left (default: 100)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
},
|
||||
{
|
||||
id: 'positionY',
|
||||
title: 'Y Position (points)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Y position from top (default: 100)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
},
|
||||
|
||||
// ========== Get Thumbnail Operation Fields ==========
|
||||
{
|
||||
id: 'thumbnailPageId',
|
||||
title: 'Slide ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Object ID of the slide to get thumbnail for',
|
||||
condition: { field: 'operation', value: 'get_thumbnail' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'thumbnailSize',
|
||||
title: 'Thumbnail Size',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Small (200px)', id: 'SMALL' },
|
||||
{ label: 'Medium (800px)', id: 'MEDIUM' },
|
||||
{ label: 'Large (1600px)', id: 'LARGE' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'get_thumbnail' },
|
||||
value: () => 'MEDIUM',
|
||||
},
|
||||
{
|
||||
id: 'mimeType',
|
||||
title: 'Image Format',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'PNG', id: 'PNG' },
|
||||
{ label: 'GIF', id: 'GIF' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'get_thumbnail' },
|
||||
value: () => 'PNG',
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'google_slides_read',
|
||||
'google_slides_write',
|
||||
'google_slides_create',
|
||||
'google_slides_replace_all_text',
|
||||
'google_slides_add_slide',
|
||||
'google_slides_add_image',
|
||||
'google_slides_get_thumbnail',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'read':
|
||||
return 'google_slides_read'
|
||||
case 'write':
|
||||
return 'google_slides_write'
|
||||
case 'create':
|
||||
return 'google_slides_create'
|
||||
case 'replace_all_text':
|
||||
return 'google_slides_replace_all_text'
|
||||
case 'add_slide':
|
||||
return 'google_slides_add_slide'
|
||||
case 'add_image':
|
||||
return 'google_slides_add_image'
|
||||
case 'get_thumbnail':
|
||||
return 'google_slides_get_thumbnail'
|
||||
default:
|
||||
throw new Error(`Invalid Google Slides operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const {
|
||||
credential,
|
||||
presentationId,
|
||||
manualPresentationId,
|
||||
folderSelector,
|
||||
folderId,
|
||||
slideIndex,
|
||||
createContent,
|
||||
thumbnailPageId,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
...rest
|
||||
} = params
|
||||
|
||||
const effectivePresentationId = (presentationId || manualPresentationId || '').trim()
|
||||
const effectiveFolderId = (folderSelector || folderId || '').trim()
|
||||
|
||||
const result: Record<string, any> = {
|
||||
...rest,
|
||||
presentationId: effectivePresentationId || undefined,
|
||||
credential,
|
||||
}
|
||||
|
||||
// Handle operation-specific params
|
||||
if (params.operation === 'write' && slideIndex) {
|
||||
result.slideIndex = Number.parseInt(slideIndex as string, 10)
|
||||
}
|
||||
|
||||
if (params.operation === 'create') {
|
||||
result.folderId = effectiveFolderId || undefined
|
||||
if (createContent) {
|
||||
result.content = createContent
|
||||
}
|
||||
}
|
||||
|
||||
if (params.operation === 'add_slide' && params.insertionIndex) {
|
||||
result.insertionIndex = Number.parseInt(params.insertionIndex as string, 10)
|
||||
}
|
||||
|
||||
if (params.operation === 'add_image') {
|
||||
if (imageWidth) {
|
||||
result.width = Number.parseInt(imageWidth as string, 10)
|
||||
}
|
||||
if (imageHeight) {
|
||||
result.height = Number.parseInt(imageHeight as string, 10)
|
||||
}
|
||||
if (params.positionX) {
|
||||
result.positionX = Number.parseInt(params.positionX as string, 10)
|
||||
}
|
||||
if (params.positionY) {
|
||||
result.positionY = Number.parseInt(params.positionY as string, 10)
|
||||
}
|
||||
}
|
||||
|
||||
if (params.operation === 'get_thumbnail') {
|
||||
result.pageObjectId = thumbnailPageId
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
credential: { type: 'string', description: 'Google Slides access token' },
|
||||
presentationId: { type: 'string', description: 'Presentation identifier' },
|
||||
manualPresentationId: { type: 'string', description: 'Manual presentation identifier' },
|
||||
// Write operation
|
||||
slideIndex: { type: 'number', description: 'Slide index to write to' },
|
||||
content: { type: 'string', description: 'Slide content' },
|
||||
// Create operation
|
||||
title: { type: 'string', description: 'Presentation title' },
|
||||
folderSelector: { type: 'string', description: 'Selected folder' },
|
||||
folderId: { type: 'string', description: 'Folder identifier' },
|
||||
createContent: { type: 'string', description: 'Initial slide content' },
|
||||
// Replace all text operation
|
||||
findText: { type: 'string', description: 'Text to find' },
|
||||
replaceText: { type: 'string', description: 'Text to replace with' },
|
||||
matchCase: { type: 'boolean', description: 'Whether to match case' },
|
||||
pageObjectIds: {
|
||||
type: 'string',
|
||||
description: 'Comma-separated slide IDs to limit replacements',
|
||||
},
|
||||
// Add slide operation
|
||||
layout: { type: 'string', description: 'Slide layout' },
|
||||
insertionIndex: { type: 'number', description: 'Position to insert slide' },
|
||||
placeholderIdMappings: { type: 'string', description: 'JSON array of placeholder ID mappings' },
|
||||
// Add image operation
|
||||
pageObjectId: { type: 'string', description: 'Slide object ID for image' },
|
||||
imageUrl: { type: 'string', description: 'Image URL' },
|
||||
imageWidth: { type: 'number', description: 'Image width in points' },
|
||||
imageHeight: { type: 'number', description: 'Image height in points' },
|
||||
positionX: { type: 'number', description: 'X position in points' },
|
||||
positionY: { type: 'number', description: 'Y position in points' },
|
||||
// Get thumbnail operation
|
||||
thumbnailPageId: { type: 'string', description: 'Slide object ID for thumbnail' },
|
||||
thumbnailSize: { type: 'string', description: 'Thumbnail size' },
|
||||
mimeType: { type: 'string', description: 'Image format (PNG or GIF)' },
|
||||
},
|
||||
outputs: {
|
||||
// Read operation
|
||||
slides: { type: 'json', description: 'Presentation slides' },
|
||||
metadata: { type: 'json', description: 'Presentation metadata' },
|
||||
// Write operation
|
||||
updatedContent: { type: 'boolean', description: 'Content update status' },
|
||||
// Replace all text operation
|
||||
occurrencesChanged: { type: 'number', description: 'Number of text occurrences replaced' },
|
||||
// Add slide operation
|
||||
slideId: { type: 'string', description: 'Object ID of newly created slide' },
|
||||
// Add image operation
|
||||
imageId: { type: 'string', description: 'Object ID of newly created image' },
|
||||
// Get thumbnail operation
|
||||
contentUrl: { type: 'string', description: 'URL to the thumbnail image' },
|
||||
width: { type: 'number', description: 'Thumbnail width in pixels' },
|
||||
height: { type: 'number', description: 'Thumbnail height in pixels' },
|
||||
},
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import { AuthMode } from '@/blocks/types'
|
||||
export const KalshiBlock: BlockConfig = {
|
||||
type: 'kalshi',
|
||||
name: 'Kalshi',
|
||||
description: 'Access prediction markets data from Kalshi',
|
||||
description: 'Access prediction markets and trade on Kalshi',
|
||||
longDescription:
|
||||
'Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, and exchange status.',
|
||||
'Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, exchange status, and place/cancel/amend trades.',
|
||||
docsLink: 'https://docs.sim.ai/tools/kalshi',
|
||||
authMode: AuthMode.ApiKey,
|
||||
category: 'tools',
|
||||
@@ -26,12 +26,16 @@ export const KalshiBlock: BlockConfig = {
|
||||
{ label: 'Get Balance', id: 'get_balance' },
|
||||
{ label: 'Get Positions', id: 'get_positions' },
|
||||
{ label: 'Get Orders', id: 'get_orders' },
|
||||
{ label: 'Get Order', id: 'get_order' },
|
||||
{ label: 'Get Orderbook', id: 'get_orderbook' },
|
||||
{ label: 'Get Trades', id: 'get_trades' },
|
||||
{ label: 'Get Candlesticks', id: 'get_candlesticks' },
|
||||
{ label: 'Get Fills', id: 'get_fills' },
|
||||
{ label: 'Get Series by Ticker', id: 'get_series_by_ticker' },
|
||||
{ label: 'Get Exchange Status', id: 'get_exchange_status' },
|
||||
{ label: 'Create Order', id: 'create_order' },
|
||||
{ label: 'Cancel Order', id: 'cancel_order' },
|
||||
{ label: 'Amend Order', id: 'amend_order' },
|
||||
],
|
||||
value: () => 'get_markets',
|
||||
},
|
||||
@@ -43,7 +47,16 @@ export const KalshiBlock: BlockConfig = {
|
||||
placeholder: 'Your Kalshi API Key ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_balance', 'get_positions', 'get_orders', 'get_fills'],
|
||||
value: [
|
||||
'get_balance',
|
||||
'get_positions',
|
||||
'get_orders',
|
||||
'get_order',
|
||||
'get_fills',
|
||||
'create_order',
|
||||
'cancel_order',
|
||||
'amend_order',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
@@ -55,7 +68,16 @@ export const KalshiBlock: BlockConfig = {
|
||||
placeholder: 'Your RSA Private Key (PEM format)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_balance', 'get_positions', 'get_orders', 'get_fills'],
|
||||
value: [
|
||||
'get_balance',
|
||||
'get_positions',
|
||||
'get_orders',
|
||||
'get_order',
|
||||
'get_fills',
|
||||
'create_order',
|
||||
'cancel_order',
|
||||
'amend_order',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
@@ -147,35 +169,20 @@ export const KalshiBlock: BlockConfig = {
|
||||
],
|
||||
condition: { field: 'operation', value: ['get_orders'] },
|
||||
},
|
||||
// Get Orderbook fields
|
||||
{
|
||||
id: 'depth',
|
||||
title: 'Depth',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of price levels per side',
|
||||
condition: { field: 'operation', value: ['get_orderbook'] },
|
||||
},
|
||||
// Get Trades fields
|
||||
{
|
||||
id: 'tickerTrades',
|
||||
title: 'Market Ticker',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by market ticker (optional)',
|
||||
condition: { field: 'operation', value: ['get_trades'] },
|
||||
},
|
||||
// Get Fills timestamp filters
|
||||
{
|
||||
id: 'minTs',
|
||||
title: 'Min Timestamp',
|
||||
type: 'short-input',
|
||||
placeholder: 'Minimum timestamp (Unix milliseconds)',
|
||||
condition: { field: 'operation', value: ['get_trades', 'get_fills'] },
|
||||
condition: { field: 'operation', value: ['get_fills'] },
|
||||
},
|
||||
{
|
||||
id: 'maxTs',
|
||||
title: 'Max Timestamp',
|
||||
type: 'short-input',
|
||||
placeholder: 'Maximum timestamp (Unix milliseconds)',
|
||||
condition: { field: 'operation', value: ['get_trades', 'get_fills'] },
|
||||
condition: { field: 'operation', value: ['get_fills'] },
|
||||
},
|
||||
// Get Candlesticks fields
|
||||
{
|
||||
@@ -198,14 +205,16 @@ export const KalshiBlock: BlockConfig = {
|
||||
id: 'startTs',
|
||||
title: 'Start Timestamp',
|
||||
type: 'short-input',
|
||||
placeholder: 'Start timestamp (Unix milliseconds)',
|
||||
placeholder: 'Start timestamp (Unix seconds)',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_candlesticks'] },
|
||||
},
|
||||
{
|
||||
id: 'endTs',
|
||||
title: 'End Timestamp',
|
||||
type: 'short-input',
|
||||
placeholder: 'End timestamp (Unix milliseconds)',
|
||||
placeholder: 'End timestamp (Unix seconds)',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_candlesticks'] },
|
||||
},
|
||||
{
|
||||
@@ -213,11 +222,11 @@ export const KalshiBlock: BlockConfig = {
|
||||
title: 'Period Interval',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All', id: '' },
|
||||
{ label: '1 minute', id: '1' },
|
||||
{ label: '1 hour', id: '60' },
|
||||
{ label: '1 day', id: '1440' },
|
||||
],
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_candlesticks'] },
|
||||
},
|
||||
// Get Fills fields
|
||||
@@ -244,6 +253,146 @@ export const KalshiBlock: BlockConfig = {
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_series_by_ticker'] },
|
||||
},
|
||||
// Order ID for get_order, cancel_order, amend_order
|
||||
{
|
||||
id: 'orderIdParam',
|
||||
title: 'Order ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Order ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_order', 'cancel_order', 'amend_order'] },
|
||||
},
|
||||
// Create Order fields
|
||||
{
|
||||
id: 'tickerOrder',
|
||||
title: 'Market Ticker',
|
||||
type: 'short-input',
|
||||
placeholder: 'Market ticker (e.g., KXBTC-24DEC31)',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'side',
|
||||
title: 'Side',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Yes', id: 'yes' },
|
||||
{ label: 'No', id: 'no' },
|
||||
],
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'action',
|
||||
title: 'Action',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Buy', id: 'buy' },
|
||||
{ label: 'Sell', id: 'sell' },
|
||||
],
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'count',
|
||||
title: 'Contracts',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of contracts',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'countAmend',
|
||||
title: 'Contracts',
|
||||
type: 'short-input',
|
||||
placeholder: 'Updated number of contracts (optional)',
|
||||
condition: { field: 'operation', value: ['amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'orderType',
|
||||
title: 'Order Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Limit', id: 'limit' },
|
||||
{ label: 'Market', id: 'market' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'yesPrice',
|
||||
title: 'Yes Price (cents)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Yes price in cents (1-99)',
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'noPrice',
|
||||
title: 'No Price (cents)',
|
||||
type: 'short-input',
|
||||
placeholder: 'No price in cents (1-99)',
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'clientOrderId',
|
||||
title: 'Client Order ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Custom order identifier (optional)',
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'clientOrderIdAmend',
|
||||
title: 'Client Order ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Original client order ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'updatedClientOrderId',
|
||||
title: 'New Client Order ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'New client order ID after amendment',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'timeInForce',
|
||||
title: 'Time in Force',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Good Till Canceled', id: 'good_till_canceled' },
|
||||
{ label: 'Fill or Kill', id: 'fill_or_kill' },
|
||||
{ label: 'Immediate or Cancel', id: 'immediate_or_cancel' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'expirationTs',
|
||||
title: 'Expiration',
|
||||
type: 'short-input',
|
||||
placeholder: 'Unix timestamp for order expiration',
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'postOnly',
|
||||
title: 'Post Only',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: '' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'reduceOnly',
|
||||
title: 'Reduce Only',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: '' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
// Pagination fields
|
||||
{
|
||||
id: 'limit',
|
||||
@@ -289,12 +438,16 @@ export const KalshiBlock: BlockConfig = {
|
||||
'kalshi_get_balance',
|
||||
'kalshi_get_positions',
|
||||
'kalshi_get_orders',
|
||||
'kalshi_get_order',
|
||||
'kalshi_get_orderbook',
|
||||
'kalshi_get_trades',
|
||||
'kalshi_get_candlesticks',
|
||||
'kalshi_get_fills',
|
||||
'kalshi_get_series_by_ticker',
|
||||
'kalshi_get_exchange_status',
|
||||
'kalshi_create_order',
|
||||
'kalshi_cancel_order',
|
||||
'kalshi_amend_order',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
@@ -313,6 +466,8 @@ export const KalshiBlock: BlockConfig = {
|
||||
return 'kalshi_get_positions'
|
||||
case 'get_orders':
|
||||
return 'kalshi_get_orders'
|
||||
case 'get_order':
|
||||
return 'kalshi_get_order'
|
||||
case 'get_orderbook':
|
||||
return 'kalshi_get_orderbook'
|
||||
case 'get_trades':
|
||||
@@ -325,6 +480,12 @@ export const KalshiBlock: BlockConfig = {
|
||||
return 'kalshi_get_series_by_ticker'
|
||||
case 'get_exchange_status':
|
||||
return 'kalshi_get_exchange_status'
|
||||
case 'create_order':
|
||||
return 'kalshi_create_order'
|
||||
case 'cancel_order':
|
||||
return 'kalshi_cancel_order'
|
||||
case 'amend_order':
|
||||
return 'kalshi_amend_order'
|
||||
default:
|
||||
return 'kalshi_get_markets'
|
||||
}
|
||||
@@ -334,11 +495,15 @@ export const KalshiBlock: BlockConfig = {
|
||||
operation,
|
||||
orderStatus,
|
||||
tickerFilter,
|
||||
tickerTrades,
|
||||
tickerFills,
|
||||
tickerCandlesticks,
|
||||
seriesTickerCandlesticks,
|
||||
seriesTickerGet,
|
||||
orderIdParam,
|
||||
tickerOrder,
|
||||
orderType,
|
||||
countAmend,
|
||||
clientOrderIdAmend,
|
||||
...rest
|
||||
} = params
|
||||
const cleanParams: Record<string, any> = {}
|
||||
@@ -353,11 +518,6 @@ export const KalshiBlock: BlockConfig = {
|
||||
cleanParams.ticker = tickerFilter
|
||||
}
|
||||
|
||||
// Map tickerTrades to ticker for get_trades
|
||||
if (operation === 'get_trades' && tickerTrades) {
|
||||
cleanParams.ticker = tickerTrades
|
||||
}
|
||||
|
||||
// Map tickerFills to ticker for get_fills
|
||||
if (operation === 'get_fills' && tickerFills) {
|
||||
cleanParams.ticker = tickerFills
|
||||
@@ -374,6 +534,36 @@ export const KalshiBlock: BlockConfig = {
|
||||
cleanParams.seriesTicker = seriesTickerGet
|
||||
}
|
||||
|
||||
// Map orderIdParam to orderId for get_order, cancel_order, amend_order
|
||||
if (
|
||||
(operation === 'get_order' ||
|
||||
operation === 'cancel_order' ||
|
||||
operation === 'amend_order') &&
|
||||
orderIdParam
|
||||
) {
|
||||
cleanParams.orderId = orderIdParam
|
||||
}
|
||||
|
||||
// Map tickerOrder to ticker for create_order, amend_order
|
||||
if ((operation === 'create_order' || operation === 'amend_order') && tickerOrder) {
|
||||
cleanParams.ticker = tickerOrder
|
||||
}
|
||||
|
||||
// Map orderType to type for create_order
|
||||
if (operation === 'create_order' && orderType) {
|
||||
cleanParams.type = orderType
|
||||
}
|
||||
|
||||
// Map countAmend to count for amend_order
|
||||
if (operation === 'amend_order' && countAmend) {
|
||||
cleanParams.count = countAmend
|
||||
}
|
||||
|
||||
// Map clientOrderIdAmend to clientOrderId for amend_order
|
||||
if (operation === 'amend_order' && clientOrderIdAmend) {
|
||||
cleanParams.clientOrderId = clientOrderIdAmend
|
||||
}
|
||||
|
||||
Object.entries(rest).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
cleanParams[key] = value
|
||||
|
||||
@@ -193,17 +193,40 @@ export const PolymarketBlock: BlockConfig = {
|
||||
{
|
||||
id: 'order',
|
||||
title: 'Sort By',
|
||||
type: 'short-input',
|
||||
placeholder: 'Sort field (e.g., id, volume, liquidity)',
|
||||
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Default', id: '' },
|
||||
{ label: 'Volume', id: 'volumeNum' },
|
||||
{ label: 'Liquidity', id: 'liquidityNum' },
|
||||
{ label: 'Start Date', id: 'startDate' },
|
||||
{ label: 'End Date', id: 'endDate' },
|
||||
{ label: 'Created At', id: 'createdAt' },
|
||||
{ label: 'Updated At', id: 'updatedAt' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['get_markets'] },
|
||||
},
|
||||
{
|
||||
id: 'orderEvents',
|
||||
title: 'Sort By',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Default', id: '' },
|
||||
{ label: 'Volume', id: 'volume' },
|
||||
{ label: 'Liquidity', id: 'liquidity' },
|
||||
{ label: 'Start Date', id: 'startDate' },
|
||||
{ label: 'End Date', id: 'endDate' },
|
||||
{ label: 'Created At', id: 'createdAt' },
|
||||
{ label: 'Updated At', id: 'updatedAt' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['get_events'] },
|
||||
},
|
||||
{
|
||||
id: 'ascending',
|
||||
title: 'Sort Order',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Descending (newest first)', id: 'false' },
|
||||
{ label: 'Ascending (oldest first)', id: 'true' },
|
||||
{ label: 'Descending', id: 'false' },
|
||||
{ label: 'Ascending', id: 'true' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
|
||||
},
|
||||
@@ -298,7 +321,7 @@ export const PolymarketBlock: BlockConfig = {
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { operation, marketSlug, eventSlug, ...rest } = params
|
||||
const { operation, marketSlug, eventSlug, orderEvents, order, ...rest } = params
|
||||
const cleanParams: Record<string, any> = {}
|
||||
|
||||
// Map marketSlug to slug for get_market
|
||||
@@ -311,6 +334,13 @@ export const PolymarketBlock: BlockConfig = {
|
||||
cleanParams.slug = eventSlug
|
||||
}
|
||||
|
||||
// Map order field based on operation (markets use volumeNum/liquidityNum, events use volume/liquidity)
|
||||
if (operation === 'get_markets' && order) {
|
||||
cleanParams.order = order
|
||||
} else if (operation === 'get_events' && orderEvents) {
|
||||
cleanParams.order = orderEvents
|
||||
}
|
||||
|
||||
// Convert numeric fields from string to number for get_price_history
|
||||
if (operation === 'get_price_history') {
|
||||
if (rest.fidelity) cleanParams.fidelity = Number(rest.fidelity)
|
||||
|
||||
@@ -35,6 +35,7 @@ import { GoogleDocsBlock } from '@/blocks/blocks/google_docs'
|
||||
import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
|
||||
import { GoogleFormsBlock } from '@/blocks/blocks/google_form'
|
||||
import { GoogleSheetsBlock } from '@/blocks/blocks/google_sheets'
|
||||
import { GoogleSlidesBlock } from '@/blocks/blocks/google_slides'
|
||||
import { GoogleVaultBlock } from '@/blocks/blocks/google_vault'
|
||||
import { GrafanaBlock } from '@/blocks/blocks/grafana'
|
||||
import { GuardrailsBlock } from '@/blocks/blocks/guardrails'
|
||||
@@ -172,6 +173,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
google_forms: GoogleFormsBlock,
|
||||
google_search: GoogleSearchBlock,
|
||||
google_sheets: GoogleSheetsBlock,
|
||||
google_slides: GoogleSlidesBlock,
|
||||
google_vault: GoogleVaultBlock,
|
||||
hubspot: HubSpotBlock,
|
||||
huggingface: HuggingFaceBlock,
|
||||
|
||||
303
apps/sim/components/emcn/components/code/code-optimized.tsx
Normal file
303
apps/sim/components/emcn/components/code/code-optimized.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
'use client'
|
||||
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { highlight, languages } from 'prismjs'
|
||||
import { List, type RowComponentProps, useDynamicRowHeight, useListRef } from 'react-window'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-python'
|
||||
import 'prismjs/components/prism-json'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { CODE_LINE_HEIGHT_PX, calculateGutterWidth } from './code'
|
||||
|
||||
/**
|
||||
* Virtualized code viewer for large outputs.
|
||||
* Uses react-window to render only visible lines, keeping DOM minimal.
|
||||
* Supports Prism syntax highlighting, line numbers, text wrapping, and search.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <VirtualizedCodeViewer
|
||||
* code={JSON.stringify(data, null, 2)}
|
||||
* showGutter
|
||||
* language="json"
|
||||
* wrapText
|
||||
* searchQuery="error"
|
||||
* currentMatchIndex={0}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* Props for the VirtualizedCodeViewer component.
|
||||
*/
|
||||
interface VirtualizedCodeViewerProps {
|
||||
/** Code content to display */
|
||||
code: string
|
||||
/** Whether to show line numbers gutter */
|
||||
showGutter?: boolean
|
||||
/** Language for syntax highlighting */
|
||||
language?: 'javascript' | 'json' | 'python'
|
||||
/** Additional CSS classes for the container */
|
||||
className?: string
|
||||
/** Left padding offset */
|
||||
paddingLeft?: number
|
||||
/** Inline styles for the gutter */
|
||||
gutterStyle?: React.CSSProperties
|
||||
/** Whether to wrap text */
|
||||
wrapText?: boolean
|
||||
/** Search query to highlight in the code */
|
||||
searchQuery?: string
|
||||
/** Index of the currently active match */
|
||||
currentMatchIndex?: number
|
||||
/** Callback when match count changes */
|
||||
onMatchCountChange?: (count: number) => void
|
||||
/** Ref for the content container */
|
||||
contentRef?: React.RefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
interface HighlightedLine {
|
||||
lineNumber: number
|
||||
html: string
|
||||
}
|
||||
|
||||
interface CodeRowProps {
|
||||
lines: HighlightedLine[]
|
||||
gutterWidth: number
|
||||
showGutter: boolean
|
||||
gutterStyle?: React.CSSProperties
|
||||
leftOffset: number
|
||||
wrapText: boolean
|
||||
}
|
||||
|
||||
function escapeRegex(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
function countSearchMatches(code: string, searchQuery: string): number {
|
||||
if (!searchQuery.trim()) return 0
|
||||
const escaped = escapeRegex(searchQuery)
|
||||
const regex = new RegExp(escaped, 'gi')
|
||||
const matches = code.match(regex)
|
||||
return matches?.length ?? 0
|
||||
}
|
||||
|
||||
function applySearchHighlightingToLine(
|
||||
html: string,
|
||||
searchQuery: string,
|
||||
currentMatchIndex: number,
|
||||
globalMatchOffset: number
|
||||
): { html: string; matchesInLine: number } {
|
||||
if (!searchQuery.trim()) return { html, matchesInLine: 0 }
|
||||
|
||||
const escaped = escapeRegex(searchQuery)
|
||||
const regex = new RegExp(`(${escaped})`, 'gi')
|
||||
const parts = html.split(/(<[^>]+>)/g)
|
||||
let matchesInLine = 0
|
||||
|
||||
const result = parts
|
||||
.map((part) => {
|
||||
if (part.startsWith('<') && part.endsWith('>')) {
|
||||
return part
|
||||
}
|
||||
return part.replace(regex, (match) => {
|
||||
const globalIndex = globalMatchOffset + matchesInLine
|
||||
const isCurrentMatch = globalIndex === currentMatchIndex
|
||||
matchesInLine++
|
||||
|
||||
const bgClass = isCurrentMatch
|
||||
? 'bg-[#F6AD55] text-[#1a1a1a] dark:bg-[#F6AD55] dark:text-[#1a1a1a]'
|
||||
: 'bg-[#FCD34D]/40 dark:bg-[#FCD34D]/30'
|
||||
|
||||
return `<mark class="${bgClass} rounded-[2px]" data-search-match>${match}</mark>`
|
||||
})
|
||||
})
|
||||
.join('')
|
||||
|
||||
return { html: result, matchesInLine }
|
||||
}
|
||||
|
||||
function CodeRow({ index, style, ...props }: RowComponentProps<CodeRowProps>) {
|
||||
const { lines, gutterWidth, showGutter, gutterStyle, leftOffset, wrapText } = props
|
||||
const line = lines[index]
|
||||
|
||||
return (
|
||||
<div style={style} className='flex' data-row-index={index}>
|
||||
{showGutter && (
|
||||
<div
|
||||
className='flex-shrink-0 select-none pr-0.5 text-right text-[var(--text-muted)] text-xs tabular-nums leading-[21px] dark:text-[#a8a8a8]'
|
||||
style={{ width: gutterWidth, marginLeft: leftOffset, ...gutterStyle }}
|
||||
>
|
||||
{line.lineNumber}
|
||||
</div>
|
||||
)}
|
||||
<pre
|
||||
className={cn(
|
||||
'm-0 flex-1 pr-2 pl-2 font-mono text-[13px] text-[var(--text-primary)] leading-[21px] dark:text-[#eeeeee]',
|
||||
wrapText ? 'whitespace-pre-wrap break-words' : 'whitespace-pre'
|
||||
)}
|
||||
dangerouslySetInnerHTML={{ __html: line.html || ' ' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const VirtualizedCodeViewer = memo(function VirtualizedCodeViewer({
|
||||
code,
|
||||
showGutter = true,
|
||||
language = 'json',
|
||||
className,
|
||||
paddingLeft = 0,
|
||||
gutterStyle,
|
||||
wrapText = false,
|
||||
searchQuery,
|
||||
currentMatchIndex = 0,
|
||||
onMatchCountChange,
|
||||
contentRef,
|
||||
}: VirtualizedCodeViewerProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const listRef = useListRef(null)
|
||||
const [containerHeight, setContainerHeight] = useState(400)
|
||||
|
||||
const dynamicRowHeight = useDynamicRowHeight({
|
||||
defaultRowHeight: CODE_LINE_HEIGHT_PX,
|
||||
key: wrapText ? 'wrap' : 'nowrap',
|
||||
})
|
||||
|
||||
const matchCount = useMemo(() => countSearchMatches(code, searchQuery || ''), [code, searchQuery])
|
||||
|
||||
useEffect(() => {
|
||||
onMatchCountChange?.(matchCount)
|
||||
}, [matchCount, onMatchCountChange])
|
||||
|
||||
const lines = useMemo(() => code.split('\n'), [code])
|
||||
const lineCount = lines.length
|
||||
const gutterWidth = useMemo(() => calculateGutterWidth(lineCount), [lineCount])
|
||||
|
||||
const highlightedLines = useMemo(() => {
|
||||
const lang = languages[language] || languages.javascript
|
||||
return lines.map((line, idx) => ({
|
||||
lineNumber: idx + 1,
|
||||
html: highlight(line, lang, language),
|
||||
}))
|
||||
}, [lines, language])
|
||||
|
||||
const matchOffsets = useMemo(() => {
|
||||
if (!searchQuery?.trim()) return []
|
||||
const offsets: number[] = []
|
||||
let cumulative = 0
|
||||
const escaped = escapeRegex(searchQuery)
|
||||
const regex = new RegExp(escaped, 'gi')
|
||||
|
||||
for (const line of lines) {
|
||||
offsets.push(cumulative)
|
||||
const matches = line.match(regex)
|
||||
cumulative += matches?.length ?? 0
|
||||
}
|
||||
return offsets
|
||||
}, [lines, searchQuery])
|
||||
|
||||
const linesWithSearch = useMemo(() => {
|
||||
if (!searchQuery?.trim()) return highlightedLines
|
||||
|
||||
return highlightedLines.map((line, idx) => {
|
||||
const { html } = applySearchHighlightingToLine(
|
||||
line.html,
|
||||
searchQuery,
|
||||
currentMatchIndex,
|
||||
matchOffsets[idx]
|
||||
)
|
||||
return { ...line, html }
|
||||
})
|
||||
}, [highlightedLines, searchQuery, currentMatchIndex, matchOffsets])
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchQuery?.trim() || matchCount === 0 || !listRef.current) return
|
||||
|
||||
let accumulated = 0
|
||||
for (let i = 0; i < matchOffsets.length; i++) {
|
||||
const matchesInThisLine = (matchOffsets[i + 1] ?? matchCount) - matchOffsets[i]
|
||||
if (currentMatchIndex >= accumulated && currentMatchIndex < accumulated + matchesInThisLine) {
|
||||
listRef.current.scrollToRow({ index: i, align: 'center' })
|
||||
break
|
||||
}
|
||||
accumulated += matchesInThisLine
|
||||
}
|
||||
}, [currentMatchIndex, searchQuery, matchCount, matchOffsets, listRef])
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
const parent = container.parentElement
|
||||
if (!parent) return
|
||||
|
||||
const updateHeight = () => {
|
||||
setContainerHeight(parent.clientHeight)
|
||||
}
|
||||
|
||||
updateHeight()
|
||||
|
||||
const resizeObserver = new ResizeObserver(updateHeight)
|
||||
resizeObserver.observe(parent)
|
||||
|
||||
return () => resizeObserver.disconnect()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!wrapText) return
|
||||
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
const rows = container.querySelectorAll('[data-row-index]')
|
||||
if (rows.length === 0) return
|
||||
|
||||
return dynamicRowHeight.observeRowElements(rows)
|
||||
}, [wrapText, dynamicRowHeight, linesWithSearch])
|
||||
|
||||
const setRefs = useCallback(
|
||||
(el: HTMLDivElement | null) => {
|
||||
containerRef.current = el
|
||||
if (contentRef && 'current' in contentRef) {
|
||||
contentRef.current = el
|
||||
}
|
||||
},
|
||||
[contentRef]
|
||||
)
|
||||
|
||||
const rowProps = useMemo(
|
||||
() => ({
|
||||
lines: linesWithSearch,
|
||||
gutterWidth,
|
||||
showGutter,
|
||||
gutterStyle,
|
||||
leftOffset: paddingLeft,
|
||||
wrapText,
|
||||
}),
|
||||
[linesWithSearch, gutterWidth, showGutter, gutterStyle, paddingLeft, wrapText]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setRefs}
|
||||
className={cn(
|
||||
'code-editor-theme relative rounded-[4px] border border-[var(--border-strong)]',
|
||||
'bg-[var(--surface-1)] font-medium font-mono text-sm',
|
||||
'dark:bg-[#1F1F1F]',
|
||||
className
|
||||
)}
|
||||
style={{ height: containerHeight }}
|
||||
>
|
||||
<List
|
||||
listRef={listRef}
|
||||
defaultHeight={containerHeight}
|
||||
rowCount={lineCount}
|
||||
rowHeight={wrapText ? dynamicRowHeight : CODE_LINE_HEIGHT_PX}
|
||||
rowComponent={CodeRow}
|
||||
rowProps={rowProps}
|
||||
overscanCount={5}
|
||||
className='overflow-x-auto'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -8,6 +8,7 @@ export {
|
||||
highlight,
|
||||
languages,
|
||||
} from './code/code'
|
||||
export { VirtualizedCodeViewer } from './code/code-optimized'
|
||||
export { Combobox, type ComboboxOption } from './combobox/combobox'
|
||||
export { Input } from './input/input'
|
||||
export { Label } from './label/label'
|
||||
|
||||
@@ -1084,6 +1084,27 @@ export function GoogleDocsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function GoogleSlidesIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 48 48'
|
||||
width='96px'
|
||||
height='96px'
|
||||
>
|
||||
<path
|
||||
fill='#FFC107'
|
||||
d='M37,45H11c-1.657,0-3-1.343-3-3V6c0-1.657,1.343-3,3-3h19l10,10v29C40,43.657,38.657,45,37,45z'
|
||||
/>
|
||||
<path fill='#FFECB3' d='M40 13L30 13 30 3z' />
|
||||
<path fill='#FFA000' d='M30 13L40 23 40 13z' />
|
||||
<path fill='#FFF8E1' d='M14 21H34V35H14z' />
|
||||
<path fill='#FFA000' d='M16 23H32V26H16zM16 28H28V30H16z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function GoogleCalendarIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -98,6 +98,8 @@ function resolveFileSelector(
|
||||
return { key: 'google.drive', context, allowSearch: true }
|
||||
case 'google-docs':
|
||||
return { key: 'google.drive', context, allowSearch: true }
|
||||
case 'google-slides':
|
||||
return { key: 'google.drive', context, allowSearch: true }
|
||||
case 'onedrive': {
|
||||
const key: SelectorKey = subBlock.mimeType === 'file' ? 'onedrive.files' : 'onedrive.folders'
|
||||
return { key, context, allowSearch: true }
|
||||
|
||||
@@ -482,7 +482,6 @@ export const auth = betterAuth({
|
||||
prompt: 'consent',
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/google-forms`,
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'google-vault',
|
||||
clientId: env.GOOGLE_CLIENT_ID as string,
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"@react-email/components": "^0.0.34",
|
||||
"@react-email/render": "2.0.0",
|
||||
"@trigger.dev/sdk": "4.1.2",
|
||||
"@types/react-window": "2.0.0",
|
||||
"@types/three": "0.177.0",
|
||||
"better-auth": "1.3.12",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
@@ -115,6 +116,7 @@
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"react-window": "2.2.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
|
||||
@@ -46,6 +46,13 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to skip for pagination',
|
||||
},
|
||||
select: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Comma-separated list of fields to return (e.g., url,traffic,keywords,top_keyword,value). Default: url,traffic,keywords,top_keyword,value',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
@@ -62,6 +69,9 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
// Select is required by API v3 - default to common fields if not provided
|
||||
const select = params.select || 'url,traffic,keywords,top_keyword,value'
|
||||
url.searchParams.set('select', select)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
|
||||
@@ -126,6 +126,7 @@ export interface AhrefsTopPagesParams extends AhrefsBaseParams {
|
||||
mode?: AhrefsTargetMode
|
||||
limit?: number
|
||||
offset?: number
|
||||
select?: string // Comma-separated list of fields to return (e.g., "url,traffic,keywords,top_keyword,value")
|
||||
}
|
||||
|
||||
export interface AhrefsTopPage {
|
||||
|
||||
198
apps/sim/tools/google_slides/add_image.ts
Normal file
198
apps/sim/tools/google_slides/add_image.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('GoogleSlidesAddImageTool')
|
||||
|
||||
interface AddImageParams {
|
||||
accessToken: string
|
||||
presentationId: string
|
||||
pageObjectId: string
|
||||
imageUrl: string
|
||||
width?: number
|
||||
height?: number
|
||||
positionX?: number
|
||||
positionY?: number
|
||||
}
|
||||
|
||||
interface AddImageResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
imageId: string
|
||||
metadata: {
|
||||
presentationId: string
|
||||
pageObjectId: string
|
||||
imageUrl: string
|
||||
url: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EMU (English Metric Units) conversion: 1 inch = 914400 EMU, 1 pt = 12700 EMU
|
||||
const PT_TO_EMU = 12700
|
||||
|
||||
export const addImageTool: ToolConfig<AddImageParams, AddImageResponse> = {
|
||||
id: 'google_slides_add_image',
|
||||
name: 'Add Image to Google Slides',
|
||||
description: 'Insert an image into a specific slide in a Google Slides presentation',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Slides API',
|
||||
},
|
||||
presentationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The ID of the presentation',
|
||||
},
|
||||
pageObjectId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The object ID of the slide/page to add the image to',
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The publicly accessible URL of the image (must be PNG, JPEG, or GIF, max 50MB)',
|
||||
},
|
||||
width: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Width of the image in points (default: 300)',
|
||||
},
|
||||
height: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Height of the image in points (default: 200)',
|
||||
},
|
||||
positionX: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'X position from the left edge in points (default: 100)',
|
||||
},
|
||||
positionY: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Y position from the top edge in points (default: 100)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const presentationId = params.presentationId?.trim()
|
||||
if (!presentationId) {
|
||||
throw new Error('Presentation ID is required')
|
||||
}
|
||||
return `https://slides.googleapis.com/v1/presentations/${presentationId}:batchUpdate`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const pageObjectId = params.pageObjectId?.trim()
|
||||
const imageUrl = params.imageUrl?.trim()
|
||||
|
||||
if (!pageObjectId) {
|
||||
throw new Error('Page Object ID is required')
|
||||
}
|
||||
if (!imageUrl) {
|
||||
throw new Error('Image URL is required')
|
||||
}
|
||||
|
||||
// Generate a unique object ID for the new image
|
||||
const imageObjectId = `image_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
|
||||
|
||||
// Convert points to EMU (default sizes if not specified)
|
||||
const widthEmu = (params.width || 300) * PT_TO_EMU
|
||||
const heightEmu = (params.height || 200) * PT_TO_EMU
|
||||
const translateX = (params.positionX || 100) * PT_TO_EMU
|
||||
const translateY = (params.positionY || 100) * PT_TO_EMU
|
||||
|
||||
return {
|
||||
requests: [
|
||||
{
|
||||
createImage: {
|
||||
objectId: imageObjectId,
|
||||
url: imageUrl,
|
||||
elementProperties: {
|
||||
pageObjectId: pageObjectId,
|
||||
size: {
|
||||
width: {
|
||||
magnitude: widthEmu,
|
||||
unit: 'EMU',
|
||||
},
|
||||
height: {
|
||||
magnitude: heightEmu,
|
||||
unit: 'EMU',
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
translateX: translateX,
|
||||
translateY: translateY,
|
||||
unit: 'EMU',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Google Slides API error:', { data })
|
||||
throw new Error(data.error?.message || 'Failed to add image')
|
||||
}
|
||||
|
||||
// The response contains the created image's object ID
|
||||
const createImageReply = data.replies?.[0]?.createImage
|
||||
const imageId = createImageReply?.objectId || ''
|
||||
|
||||
const presentationId = params?.presentationId?.trim() || ''
|
||||
const pageObjectId = params?.pageObjectId?.trim() || ''
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
imageId,
|
||||
metadata: {
|
||||
presentationId,
|
||||
pageObjectId,
|
||||
imageUrl: params?.imageUrl?.trim() || '',
|
||||
url: `https://docs.google.com/presentation/d/${presentationId}/edit`,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
imageId: {
|
||||
type: 'string',
|
||||
description: 'The object ID of the newly created image',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata including presentation ID and image URL',
|
||||
},
|
||||
},
|
||||
}
|
||||
187
apps/sim/tools/google_slides/add_slide.ts
Normal file
187
apps/sim/tools/google_slides/add_slide.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('GoogleSlidesAddSlideTool')
|
||||
|
||||
interface AddSlideParams {
|
||||
accessToken: string
|
||||
presentationId: string
|
||||
layout?: string
|
||||
insertionIndex?: number
|
||||
placeholderIdMappings?: string
|
||||
}
|
||||
|
||||
interface AddSlideResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
slideId: string
|
||||
metadata: {
|
||||
presentationId: string
|
||||
layout: string
|
||||
insertionIndex?: number
|
||||
url: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Predefined layouts available in Google Slides API
|
||||
const PREDEFINED_LAYOUTS = [
|
||||
'BLANK',
|
||||
'CAPTION_ONLY',
|
||||
'TITLE',
|
||||
'TITLE_AND_BODY',
|
||||
'TITLE_AND_TWO_COLUMNS',
|
||||
'TITLE_ONLY',
|
||||
'SECTION_HEADER',
|
||||
'SECTION_TITLE_AND_DESCRIPTION',
|
||||
'ONE_COLUMN_TEXT',
|
||||
'MAIN_POINT',
|
||||
'BIG_NUMBER',
|
||||
] as const
|
||||
|
||||
export const addSlideTool: ToolConfig<AddSlideParams, AddSlideResponse> = {
|
||||
id: 'google_slides_add_slide',
|
||||
name: 'Add Slide to Google Slides',
|
||||
description: 'Add a new slide to a Google Slides presentation with a specified layout',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Slides API',
|
||||
},
|
||||
presentationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The ID of the presentation',
|
||||
},
|
||||
layout: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description:
|
||||
'The predefined layout for the slide (BLANK, TITLE, TITLE_AND_BODY, TITLE_ONLY, SECTION_HEADER, etc.). Defaults to BLANK.',
|
||||
},
|
||||
insertionIndex: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description:
|
||||
'The optional zero-based index indicating where to insert the slide. If not specified, the slide is added at the end.',
|
||||
},
|
||||
placeholderIdMappings: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description:
|
||||
'JSON array of placeholder mappings to assign custom object IDs to placeholders. Format: [{"layoutPlaceholder":{"type":"TITLE"},"objectId":"custom_title_id"}]',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const presentationId = params.presentationId?.trim()
|
||||
if (!presentationId) {
|
||||
throw new Error('Presentation ID is required')
|
||||
}
|
||||
return `https://slides.googleapis.com/v1/presentations/${presentationId}:batchUpdate`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
// Generate a unique object ID for the new slide
|
||||
const slideObjectId = `slide_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
|
||||
|
||||
// Validate and normalize the layout
|
||||
let layout = (params.layout || 'BLANK').toUpperCase()
|
||||
if (!PREDEFINED_LAYOUTS.includes(layout as (typeof PREDEFINED_LAYOUTS)[number])) {
|
||||
logger.warn(`Invalid layout "${params.layout}", defaulting to BLANK`)
|
||||
layout = 'BLANK'
|
||||
}
|
||||
|
||||
const createSlideRequest: Record<string, any> = {
|
||||
objectId: slideObjectId,
|
||||
slideLayoutReference: {
|
||||
predefinedLayout: layout,
|
||||
},
|
||||
}
|
||||
|
||||
// Add insertion index if specified
|
||||
if (params.insertionIndex !== undefined && params.insertionIndex >= 0) {
|
||||
createSlideRequest.insertionIndex = params.insertionIndex
|
||||
}
|
||||
|
||||
// Add placeholder ID mappings if specified (for advanced use cases)
|
||||
if (params.placeholderIdMappings?.trim()) {
|
||||
try {
|
||||
const mappings = JSON.parse(params.placeholderIdMappings)
|
||||
if (Array.isArray(mappings) && mappings.length > 0) {
|
||||
createSlideRequest.placeholderIdMappings = mappings
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Invalid placeholderIdMappings JSON, ignoring:', e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
requests: [
|
||||
{
|
||||
createSlide: createSlideRequest,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Google Slides API error:', { data })
|
||||
throw new Error(data.error?.message || 'Failed to add slide')
|
||||
}
|
||||
|
||||
// The response contains the created slide's object ID
|
||||
const createSlideReply = data.replies?.[0]?.createSlide
|
||||
const slideId = createSlideReply?.objectId || ''
|
||||
|
||||
const presentationId = params?.presentationId?.trim() || ''
|
||||
const layout = (params?.layout || 'BLANK').toUpperCase()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
slideId,
|
||||
metadata: {
|
||||
presentationId,
|
||||
layout,
|
||||
insertionIndex: params?.insertionIndex,
|
||||
url: `https://docs.google.com/presentation/d/${presentationId}/edit`,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
slideId: {
|
||||
type: 'string',
|
||||
description: 'The object ID of the newly created slide',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata including presentation ID, layout, and URL',
|
||||
},
|
||||
},
|
||||
}
|
||||
158
apps/sim/tools/google_slides/create.ts
Normal file
158
apps/sim/tools/google_slides/create.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
GoogleSlidesCreateResponse,
|
||||
GoogleSlidesToolParams,
|
||||
} from '@/tools/google_slides/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('GoogleSlidesCreateTool')
|
||||
|
||||
export const createTool: ToolConfig<GoogleSlidesToolParams, GoogleSlidesCreateResponse> = {
|
||||
id: 'google_slides_create',
|
||||
name: 'Create Google Slides Presentation',
|
||||
description: 'Create a new Google Slides presentation',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Slides API',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The title of the presentation to create',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The content to add to the first slide',
|
||||
},
|
||||
folderSelector: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Select the folder to create the presentation in',
|
||||
},
|
||||
folderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The ID of the folder to create the presentation in (internal use)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => {
|
||||
return 'https://www.googleapis.com/drive/v3/files'
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
// Validate access token
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
if (!params.title) {
|
||||
throw new Error('Title is required')
|
||||
}
|
||||
|
||||
const requestBody: any = {
|
||||
name: params.title,
|
||||
mimeType: 'application/vnd.google-apps.presentation',
|
||||
}
|
||||
|
||||
// Add parent folder if specified (prefer folderSelector over folderId)
|
||||
const folderId = params.folderSelector || params.folderId
|
||||
if (folderId) {
|
||||
requestBody.parents = [folderId]
|
||||
}
|
||||
|
||||
return requestBody
|
||||
},
|
||||
},
|
||||
|
||||
postProcess: async (result, params, executeTool) => {
|
||||
if (!result.success) {
|
||||
return result
|
||||
}
|
||||
|
||||
const presentationId = result.output.metadata.presentationId
|
||||
|
||||
if (params.content && presentationId) {
|
||||
try {
|
||||
const writeParams = {
|
||||
accessToken: params.accessToken,
|
||||
presentationId: presentationId,
|
||||
content: params.content,
|
||||
}
|
||||
|
||||
const writeResult = await executeTool('google_slides_write', writeParams)
|
||||
|
||||
if (!writeResult.success) {
|
||||
logger.warn(
|
||||
'Failed to add content to presentation, but presentation was created:',
|
||||
writeResult.error
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('Error adding content to presentation:', { error })
|
||||
// Don't fail the overall operation if adding content fails
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
try {
|
||||
// Get the response data
|
||||
const responseText = await response.text()
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
const presentationId = data.id
|
||||
const title = data.name
|
||||
|
||||
const metadata = {
|
||||
presentationId,
|
||||
title: title || 'Untitled Presentation',
|
||||
mimeType: 'application/vnd.google-apps.presentation',
|
||||
url: `https://docs.google.com/presentation/d/${presentationId}/edit`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
metadata,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Google Slides create - Error processing response:', {
|
||||
error,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Created presentation metadata including ID, title, and URL',
|
||||
},
|
||||
},
|
||||
}
|
||||
168
apps/sim/tools/google_slides/get_thumbnail.ts
Normal file
168
apps/sim/tools/google_slides/get_thumbnail.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('GoogleSlidesGetThumbnailTool')
|
||||
|
||||
interface GetThumbnailParams {
|
||||
accessToken: string
|
||||
presentationId: string
|
||||
pageObjectId: string
|
||||
thumbnailSize?: string
|
||||
mimeType?: string
|
||||
}
|
||||
|
||||
interface GetThumbnailResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
contentUrl: string
|
||||
width: number
|
||||
height: number
|
||||
metadata: {
|
||||
presentationId: string
|
||||
pageObjectId: string
|
||||
thumbnailSize: string
|
||||
mimeType: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Available thumbnail sizes
|
||||
const THUMBNAIL_SIZES = ['SMALL', 'MEDIUM', 'LARGE'] as const
|
||||
|
||||
// Available MIME types for thumbnails
|
||||
const MIME_TYPES = ['PNG', 'GIF'] as const
|
||||
|
||||
export const getThumbnailTool: ToolConfig<GetThumbnailParams, GetThumbnailResponse> = {
|
||||
id: 'google_slides_get_thumbnail',
|
||||
name: 'Get Slide Thumbnail',
|
||||
description: 'Generate a thumbnail image of a specific slide in a Google Slides presentation',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Slides API',
|
||||
},
|
||||
presentationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The ID of the presentation',
|
||||
},
|
||||
pageObjectId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The object ID of the slide/page to get a thumbnail for',
|
||||
},
|
||||
thumbnailSize: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description:
|
||||
'The size of the thumbnail: SMALL (200px), MEDIUM (800px), or LARGE (1600px). Defaults to MEDIUM.',
|
||||
},
|
||||
mimeType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'The MIME type of the thumbnail image: PNG or GIF. Defaults to PNG.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const presentationId = params.presentationId?.trim()
|
||||
const pageObjectId = params.pageObjectId?.trim()
|
||||
|
||||
if (!presentationId) {
|
||||
throw new Error('Presentation ID is required')
|
||||
}
|
||||
if (!pageObjectId) {
|
||||
throw new Error('Page Object ID is required')
|
||||
}
|
||||
|
||||
// Build the URL with query parameters for thumbnail properties
|
||||
let size = (params.thumbnailSize || 'MEDIUM').toUpperCase()
|
||||
if (!THUMBNAIL_SIZES.includes(size as (typeof THUMBNAIL_SIZES)[number])) {
|
||||
size = 'MEDIUM'
|
||||
}
|
||||
|
||||
// Validate and normalize mimeType
|
||||
let mimeType = (params.mimeType || 'PNG').toUpperCase()
|
||||
if (!MIME_TYPES.includes(mimeType as (typeof MIME_TYPES)[number])) {
|
||||
mimeType = 'PNG'
|
||||
}
|
||||
|
||||
// The API uses thumbnailProperties as query parameters
|
||||
let url = `https://slides.googleapis.com/v1/presentations/${presentationId}/pages/${pageObjectId}/thumbnail?thumbnailProperties.thumbnailSize=${size}`
|
||||
|
||||
// Add mimeType if not the default (PNG)
|
||||
if (mimeType !== 'PNG') {
|
||||
url += `&thumbnailProperties.mimeType=${mimeType}`
|
||||
}
|
||||
|
||||
return url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Google Slides API error:', { data })
|
||||
throw new Error(data.error?.message || 'Failed to get thumbnail')
|
||||
}
|
||||
|
||||
const presentationId = params?.presentationId?.trim() || ''
|
||||
const pageObjectId = params?.pageObjectId?.trim() || ''
|
||||
const thumbnailSize = (params?.thumbnailSize || 'MEDIUM').toUpperCase()
|
||||
const mimeType = (params?.mimeType || 'PNG').toUpperCase()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contentUrl: data.contentUrl,
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
metadata: {
|
||||
presentationId,
|
||||
pageObjectId,
|
||||
thumbnailSize,
|
||||
mimeType,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
contentUrl: {
|
||||
type: 'string',
|
||||
description: 'URL to the thumbnail image (valid for 30 minutes)',
|
||||
},
|
||||
width: {
|
||||
type: 'number',
|
||||
description: 'Width of the thumbnail in pixels',
|
||||
},
|
||||
height: {
|
||||
type: 'number',
|
||||
description: 'Height of the thumbnail in pixels',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata including presentation ID and page object ID',
|
||||
},
|
||||
},
|
||||
}
|
||||
15
apps/sim/tools/google_slides/index.ts
Normal file
15
apps/sim/tools/google_slides/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { addImageTool } from '@/tools/google_slides/add_image'
|
||||
import { addSlideTool } from '@/tools/google_slides/add_slide'
|
||||
import { createTool } from '@/tools/google_slides/create'
|
||||
import { getThumbnailTool } from '@/tools/google_slides/get_thumbnail'
|
||||
import { readTool } from '@/tools/google_slides/read'
|
||||
import { replaceAllTextTool } from '@/tools/google_slides/replace_all_text'
|
||||
import { writeTool } from '@/tools/google_slides/write'
|
||||
|
||||
export const googleSlidesReadTool = readTool
|
||||
export const googleSlidesWriteTool = writeTool
|
||||
export const googleSlidesCreateTool = createTool
|
||||
export const googleSlidesReplaceAllTextTool = replaceAllTextTool
|
||||
export const googleSlidesAddSlideTool = addSlideTool
|
||||
export const googleSlidesGetThumbnailTool = getThumbnailTool
|
||||
export const googleSlidesAddImageTool = addImageTool
|
||||
84
apps/sim/tools/google_slides/read.ts
Normal file
84
apps/sim/tools/google_slides/read.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { GoogleSlidesReadResponse, GoogleSlidesToolParams } from '@/tools/google_slides/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const readTool: ToolConfig<GoogleSlidesToolParams, GoogleSlidesReadResponse> = {
|
||||
id: 'google_slides_read',
|
||||
name: 'Read Google Slides Presentation',
|
||||
description: 'Read content from a Google Slides presentation',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Slides API',
|
||||
},
|
||||
presentationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the presentation to read',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
// Ensure presentationId is valid
|
||||
const presentationId = params.presentationId?.trim() || params.manualPresentationId?.trim()
|
||||
if (!presentationId) {
|
||||
throw new Error('Presentation ID is required')
|
||||
}
|
||||
|
||||
return `https://slides.googleapis.com/v1/presentations/${presentationId}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
// Validate access token
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// Extract slides from the response
|
||||
const slides = data.slides || []
|
||||
|
||||
// Create presentation metadata
|
||||
const metadata = {
|
||||
presentationId: data.presentationId,
|
||||
title: data.title || 'Untitled Presentation',
|
||||
pageSize: data.pageSize,
|
||||
mimeType: 'application/vnd.google-apps.presentation',
|
||||
url: `https://docs.google.com/presentation/d/${data.presentationId}/edit`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
slides,
|
||||
metadata,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
slides: { type: 'json', description: 'Array of slides with their content' },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Presentation metadata including ID, title, and URL',
|
||||
},
|
||||
},
|
||||
}
|
||||
164
apps/sim/tools/google_slides/replace_all_text.ts
Normal file
164
apps/sim/tools/google_slides/replace_all_text.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('GoogleSlidesReplaceAllTextTool')
|
||||
|
||||
interface ReplaceAllTextParams {
|
||||
accessToken: string
|
||||
presentationId: string
|
||||
findText: string
|
||||
replaceText: string
|
||||
matchCase?: boolean
|
||||
pageObjectIds?: string
|
||||
}
|
||||
|
||||
interface ReplaceAllTextResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
occurrencesChanged: number
|
||||
metadata: {
|
||||
presentationId: string
|
||||
findText: string
|
||||
replaceText: string
|
||||
url: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const replaceAllTextTool: ToolConfig<ReplaceAllTextParams, ReplaceAllTextResponse> = {
|
||||
id: 'google_slides_replace_all_text',
|
||||
name: 'Replace All Text in Google Slides',
|
||||
description: 'Find and replace all occurrences of text throughout a Google Slides presentation',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Slides API',
|
||||
},
|
||||
presentationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The ID of the presentation',
|
||||
},
|
||||
findText: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The text to find (e.g., {{placeholder}})',
|
||||
},
|
||||
replaceText: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The text to replace with',
|
||||
},
|
||||
matchCase: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'Whether the search should be case-sensitive (default: true)',
|
||||
},
|
||||
pageObjectIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description:
|
||||
'Comma-separated list of slide object IDs to limit replacements to specific slides (leave empty for all slides)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const presentationId = params.presentationId?.trim()
|
||||
if (!presentationId) {
|
||||
throw new Error('Presentation ID is required')
|
||||
}
|
||||
return `https://slides.googleapis.com/v1/presentations/${presentationId}:batchUpdate`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
if (!params.findText) {
|
||||
throw new Error('Find text is required')
|
||||
}
|
||||
if (params.replaceText === undefined || params.replaceText === null) {
|
||||
throw new Error('Replace text is required')
|
||||
}
|
||||
|
||||
const replaceAllTextRequest: Record<string, any> = {
|
||||
containsText: {
|
||||
text: params.findText,
|
||||
matchCase: params.matchCase !== false, // Default to true
|
||||
},
|
||||
replaceText: params.replaceText,
|
||||
}
|
||||
|
||||
// Add pageObjectIds if specified to limit replacements to specific slides
|
||||
if (params.pageObjectIds?.trim()) {
|
||||
replaceAllTextRequest.pageObjectIds = params.pageObjectIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id.length > 0)
|
||||
}
|
||||
|
||||
return {
|
||||
requests: [
|
||||
{
|
||||
replaceAllText: replaceAllTextRequest,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Google Slides API error:', { data })
|
||||
throw new Error(data.error?.message || 'Failed to replace text')
|
||||
}
|
||||
|
||||
// The response contains replies array with replaceAllText results
|
||||
const replaceResult = data.replies?.[0]?.replaceAllText
|
||||
const occurrencesChanged = replaceResult?.occurrencesChanged || 0
|
||||
|
||||
const presentationId = params?.presentationId?.trim() || ''
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
occurrencesChanged,
|
||||
metadata: {
|
||||
presentationId,
|
||||
findText: params?.findText || '',
|
||||
replaceText: params?.replaceText || '',
|
||||
url: `https://docs.google.com/presentation/d/${presentationId}/edit`,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
occurrencesChanged: {
|
||||
type: 'number',
|
||||
description: 'Number of text occurrences that were replaced',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata including presentation ID and URL',
|
||||
},
|
||||
},
|
||||
}
|
||||
50
apps/sim/tools/google_slides/types.ts
Normal file
50
apps/sim/tools/google_slides/types.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface GoogleSlidesMetadata {
|
||||
presentationId: string
|
||||
title: string
|
||||
pageSize?: {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
mimeType?: string
|
||||
createdTime?: string
|
||||
modifiedTime?: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
export interface GoogleSlidesReadResponse extends ToolResponse {
|
||||
output: {
|
||||
slides: any[]
|
||||
metadata: GoogleSlidesMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleSlidesWriteResponse extends ToolResponse {
|
||||
output: {
|
||||
updatedContent: boolean
|
||||
metadata: GoogleSlidesMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleSlidesCreateResponse extends ToolResponse {
|
||||
output: {
|
||||
metadata: GoogleSlidesMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleSlidesToolParams {
|
||||
accessToken: string
|
||||
presentationId?: string
|
||||
manualPresentationId?: string
|
||||
title?: string
|
||||
content?: string
|
||||
slideIndex?: number
|
||||
folderId?: string
|
||||
folderSelector?: string
|
||||
}
|
||||
|
||||
export type GoogleSlidesResponse =
|
||||
| GoogleSlidesReadResponse
|
||||
| GoogleSlidesWriteResponse
|
||||
| GoogleSlidesCreateResponse
|
||||
207
apps/sim/tools/google_slides/write.ts
Normal file
207
apps/sim/tools/google_slides/write.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { GoogleSlidesToolParams, GoogleSlidesWriteResponse } from '@/tools/google_slides/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('GoogleSlidesWriteTool')
|
||||
|
||||
export const writeTool: ToolConfig<GoogleSlidesToolParams, GoogleSlidesWriteResponse> = {
|
||||
id: 'google_slides_write',
|
||||
name: 'Write to Google Slides Presentation',
|
||||
description: 'Write or update content in a Google Slides presentation',
|
||||
version: '1.0',
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
},
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Google Slides API',
|
||||
},
|
||||
presentationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The ID of the presentation to write to',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The content to write to the slide',
|
||||
},
|
||||
slideIndex: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'The index of the slide to write to (defaults to first slide)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
// Ensure presentationId is valid
|
||||
const presentationId = params.presentationId?.trim() || params.manualPresentationId?.trim()
|
||||
if (!presentationId) {
|
||||
throw new Error('Presentation ID is required')
|
||||
}
|
||||
|
||||
// First, we'll read the presentation to get slide information
|
||||
return `https://slides.googleapis.com/v1/presentations/${presentationId}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
// Validate access token
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
postProcess: async (result, params, _executeTool) => {
|
||||
if (!result.success) {
|
||||
return result
|
||||
}
|
||||
|
||||
// Validate content
|
||||
if (!params.content) {
|
||||
throw new Error('Content is required')
|
||||
}
|
||||
|
||||
const presentationId = params.presentationId?.trim() || params.manualPresentationId?.trim()
|
||||
|
||||
if (!presentationId) {
|
||||
throw new Error('Presentation ID is required')
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the presentation data from the initial read
|
||||
const presentationData = await fetch(
|
||||
`https://slides.googleapis.com/v1/presentations/${presentationId}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
},
|
||||
}
|
||||
).then((res) => res.json())
|
||||
|
||||
const slideIndex = params.slideIndex || 0
|
||||
const slide = presentationData.slides?.[slideIndex]
|
||||
|
||||
if (!slide) {
|
||||
throw new Error(`Slide at index ${slideIndex} not found`)
|
||||
}
|
||||
|
||||
// Create requests to add content to the slide
|
||||
const textBoxId = `textbox_${Date.now()}`
|
||||
const requests = [
|
||||
{
|
||||
createShape: {
|
||||
objectId: textBoxId,
|
||||
shapeType: 'TEXT_BOX',
|
||||
elementProperties: {
|
||||
pageObjectId: slide.objectId,
|
||||
size: {
|
||||
width: {
|
||||
magnitude: 400,
|
||||
unit: 'PT',
|
||||
},
|
||||
height: {
|
||||
magnitude: 100,
|
||||
unit: 'PT',
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
translateX: 50,
|
||||
translateY: 100,
|
||||
unit: 'PT',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
insertText: {
|
||||
objectId: textBoxId,
|
||||
text: params.content,
|
||||
insertionIndex: 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// Make the batchUpdate request
|
||||
const updateResponse = await fetch(
|
||||
`https://slides.googleapis.com/v1/presentations/${presentationId}:batchUpdate`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ requests }),
|
||||
}
|
||||
)
|
||||
|
||||
if (!updateResponse.ok) {
|
||||
const errorText = await updateResponse.text()
|
||||
logger.error('Failed to update presentation:', { errorText })
|
||||
throw new Error('Failed to update presentation')
|
||||
}
|
||||
|
||||
// Create presentation metadata
|
||||
const metadata = {
|
||||
presentationId,
|
||||
title: presentationData.title || 'Updated Presentation',
|
||||
mimeType: 'application/vnd.google-apps.presentation',
|
||||
url: `https://docs.google.com/presentation/d/${presentationId}/edit`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
updatedContent: true,
|
||||
metadata,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in postProcess:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
updatedContent: {
|
||||
type: 'boolean',
|
||||
description: 'Indicates if presentation content was updated successfully',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Updated presentation metadata including ID, title, and URL',
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
// This is just for the initial read, the actual response comes from postProcess
|
||||
const data = await response.json()
|
||||
|
||||
const metadata = {
|
||||
presentationId: data.presentationId,
|
||||
title: data.title || 'Presentation',
|
||||
mimeType: 'application/vnd.google-apps.presentation',
|
||||
url: `https://docs.google.com/presentation/d/${data.presentationId}/edit`,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
updatedContent: false,
|
||||
metadata,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -38,6 +38,13 @@ export const schedulesUpdateTool: ToolConfig<
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New timezone for the schedule (e.g., America/New_York)',
|
||||
},
|
||||
config: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Schedule configuration as JSON string with rotations. Example: {"rotations": [{"name": "Primary", "users": [{"id": "user_id"}], "handover_start_at": "2024-01-01T09:00:00Z", "handovers": [{"interval": 1, "interval_type": "weekly"}]}]}',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
@@ -51,6 +58,10 @@ export const schedulesUpdateTool: ToolConfig<
|
||||
const schedule: Record<string, any> = {}
|
||||
if (params.name) schedule.name = params.name
|
||||
if (params.timezone) schedule.timezone = params.timezone
|
||||
if (params.config) {
|
||||
schedule.config =
|
||||
typeof params.config === 'string' ? JSON.parse(params.config) : params.config
|
||||
}
|
||||
return { schedule }
|
||||
},
|
||||
},
|
||||
|
||||
@@ -262,6 +262,16 @@ export interface WorkflowsCreateParams extends IncidentioBaseParams {
|
||||
name: string
|
||||
folder?: string
|
||||
state?: 'active' | 'draft' | 'disabled'
|
||||
trigger?: string
|
||||
steps?: string
|
||||
condition_groups?: string
|
||||
runs_on_incidents?: 'newly_created' | 'newly_created_and_active' | 'active' | 'all'
|
||||
runs_on_incident_modes?: string
|
||||
include_private_incidents?: boolean
|
||||
continue_on_step_error?: boolean
|
||||
once_for?: string
|
||||
expressions?: string
|
||||
delay?: string
|
||||
}
|
||||
|
||||
export interface WorkflowsCreateResponse extends ToolResponse {
|
||||
@@ -597,6 +607,7 @@ export interface IncidentioSchedulesUpdateParams extends IncidentioBaseParams {
|
||||
id: string
|
||||
name?: string
|
||||
timezone?: string
|
||||
config?: string
|
||||
}
|
||||
|
||||
export interface IncidentioSchedulesUpdateResponse extends ToolResponse {
|
||||
|
||||
@@ -31,7 +31,83 @@ export const workflowsCreateTool: ToolConfig<WorkflowsCreateParams, WorkflowsCre
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'State of the workflow (active, draft, or disabled)',
|
||||
default: 'active',
|
||||
default: 'draft',
|
||||
},
|
||||
trigger: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Trigger type for the workflow (e.g., "incident.updated", "incident.created")',
|
||||
default: 'incident.updated',
|
||||
},
|
||||
steps: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of workflow steps as JSON string. Example: [{"label": "Notify team", "name": "slack.post_message"}]',
|
||||
default: '[]',
|
||||
},
|
||||
condition_groups: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of condition groups as JSON string to control when the workflow runs. Example: [{"conditions": [{"operation": "one_of", "param_bindings": [], "subject": "incident.severity"}]}]',
|
||||
default: '[]',
|
||||
},
|
||||
runs_on_incidents: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'When to run the workflow: "newly_created" (only new incidents), "newly_created_and_active" (new and active incidents), "active" (only active incidents), or "all" (all incidents)',
|
||||
default: 'newly_created',
|
||||
},
|
||||
runs_on_incident_modes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of incident modes to run on as JSON string. Example: ["standard", "retrospective"]',
|
||||
default: '["standard"]',
|
||||
},
|
||||
include_private_incidents: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to include private incidents',
|
||||
default: true,
|
||||
},
|
||||
continue_on_step_error: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to continue executing subsequent steps if a step fails',
|
||||
default: false,
|
||||
},
|
||||
once_for: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of fields to ensure the workflow runs only once per unique combination of these fields, as JSON string. Example: ["incident.id"]',
|
||||
default: '[]',
|
||||
},
|
||||
expressions: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of workflow expressions as JSON string for advanced workflow logic. Example: [{"label": "My expression", "operations": []}]',
|
||||
default: '[]',
|
||||
},
|
||||
delay: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Delay configuration as JSON string. Example: {"for_seconds": 60, "conditions_apply_over_delay": false}',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -43,18 +119,29 @@ export const workflowsCreateTool: ToolConfig<WorkflowsCreateParams, WorkflowsCre
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
// Helper function to safely parse JSON strings
|
||||
const parseJsonParam = (jsonString: string | undefined, defaultValue: any) => {
|
||||
if (!jsonString) return defaultValue
|
||||
try {
|
||||
return JSON.parse(jsonString)
|
||||
} catch (error) {
|
||||
console.warn(`Failed to parse JSON parameter: ${jsonString}`, error)
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// incident.io requires all these fields to create a workflow
|
||||
const body: Record<string, any> = {
|
||||
name: params.name,
|
||||
trigger: 'incident.updated',
|
||||
once_for: [],
|
||||
condition_groups: [],
|
||||
steps: [],
|
||||
expressions: [],
|
||||
include_private_incidents: true,
|
||||
runs_on_incident_modes: ['standard'],
|
||||
continue_on_step_error: false,
|
||||
runs_on_incidents: 'newly_created',
|
||||
trigger: params.trigger || 'incident.updated',
|
||||
once_for: parseJsonParam(params.once_for, []),
|
||||
condition_groups: parseJsonParam(params.condition_groups, []),
|
||||
steps: parseJsonParam(params.steps, []),
|
||||
expressions: parseJsonParam(params.expressions, []),
|
||||
include_private_incidents: params.include_private_incidents ?? true,
|
||||
runs_on_incident_modes: parseJsonParam(params.runs_on_incident_modes, ['standard']),
|
||||
continue_on_step_error: params.continue_on_step_error ?? false,
|
||||
runs_on_incidents: params.runs_on_incidents || 'newly_created',
|
||||
state: params.state || 'draft',
|
||||
}
|
||||
|
||||
@@ -62,6 +149,10 @@ export const workflowsCreateTool: ToolConfig<WorkflowsCreateParams, WorkflowsCre
|
||||
body.folder = params.folder
|
||||
}
|
||||
|
||||
if (params.delay) {
|
||||
body.delay = parseJsonParam(params.delay, undefined)
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
@@ -84,7 +84,8 @@ export const intercomCreateCompanyTool: ToolConfig<
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'How much revenue the company generates for your business',
|
||||
description:
|
||||
'How much revenue the company generates for your business. Note: This field truncates floats to whole integers (e.g., 155.98 becomes 155)',
|
||||
},
|
||||
custom_attributes: {
|
||||
type: 'string',
|
||||
|
||||
@@ -29,7 +29,8 @@ export const intercomListCompaniesTool: ToolConfig<
|
||||
> = {
|
||||
id: 'intercom_list_companies',
|
||||
name: 'List Companies from Intercom',
|
||||
description: 'List all companies from Intercom with pagination support',
|
||||
description:
|
||||
'List all companies from Intercom with pagination support. Note: This endpoint has a limit of 10,000 companies that can be returned using pagination. For datasets larger than 10,000 companies, use the Scroll API instead.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
|
||||
@@ -61,9 +61,10 @@ export const intercomReplyConversationTool: ToolConfig<
|
||||
},
|
||||
admin_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the admin authoring the reply',
|
||||
description:
|
||||
'The ID of the admin authoring the reply. If not provided, a default admin (Operator/Fin) will be used.',
|
||||
},
|
||||
attachment_urls: {
|
||||
type: 'string',
|
||||
|
||||
163
apps/sim/tools/kalshi/amend_order.ts
Normal file
163
apps/sim/tools/kalshi/amend_order.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { KalshiAuthParams, KalshiOrder } from './types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from './types'
|
||||
|
||||
export interface KalshiAmendOrderParams extends KalshiAuthParams {
|
||||
orderId: string // Order ID to amend (required)
|
||||
ticker: string // Market ticker (required)
|
||||
side: string // 'yes' or 'no' (required)
|
||||
action: string // 'buy' or 'sell' (required)
|
||||
clientOrderId: string // Original client order ID (required)
|
||||
updatedClientOrderId: string // New client order ID (required)
|
||||
count?: string // Updated quantity
|
||||
yesPrice?: string // Updated yes price in cents (1-99)
|
||||
noPrice?: string // Updated no price in cents (1-99)
|
||||
yesPriceDollars?: string // Updated yes price in dollars
|
||||
noPriceDollars?: string // Updated no price in dollars
|
||||
}
|
||||
|
||||
export interface KalshiAmendOrderResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
order: KalshiOrder
|
||||
metadata: {
|
||||
operation: 'amend_order'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiAmendOrderTool: ToolConfig<KalshiAmendOrderParams, KalshiAmendOrderResponse> = {
|
||||
id: 'kalshi_amend_order',
|
||||
name: 'Amend Order on Kalshi',
|
||||
description: 'Modify the price or quantity of an existing order on Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The order ID to amend',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Market ticker',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: "Side of the order: 'yes' or 'no'",
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: "Action type: 'buy' or 'sell'",
|
||||
},
|
||||
clientOrderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The original client-specified order ID',
|
||||
},
|
||||
updatedClientOrderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The new client-specified order ID after amendment',
|
||||
},
|
||||
count: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Updated quantity for the order',
|
||||
},
|
||||
yesPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Updated yes price in cents (1-99)',
|
||||
},
|
||||
noPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Updated no price in cents (1-99)',
|
||||
},
|
||||
yesPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Updated yes price in dollars (e.g., "0.56")',
|
||||
},
|
||||
noPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Updated no price in dollars (e.g., "0.56")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId}/amend`),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId}/amend`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'POST', path)
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
ticker: params.ticker,
|
||||
side: params.side.toLowerCase(),
|
||||
action: params.action.toLowerCase(),
|
||||
client_order_id: params.clientOrderId,
|
||||
updated_client_order_id: params.updatedClientOrderId,
|
||||
}
|
||||
|
||||
if (params.count) body.count = Number.parseInt(params.count, 10)
|
||||
if (params.yesPrice) body.yes_price = Number.parseInt(params.yesPrice, 10)
|
||||
if (params.noPrice) body.no_price = Number.parseInt(params.noPrice, 10)
|
||||
if (params.yesPriceDollars) body.yes_price_dollars = params.yesPriceDollars
|
||||
if (params.noPriceDollars) body.no_price_dollars = params.noPriceDollars
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'amend_order')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: data.order,
|
||||
metadata: {
|
||||
operation: 'amend_order' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Amended order data',
|
||||
properties: {
|
||||
order: { type: 'object', description: 'The amended order object' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
90
apps/sim/tools/kalshi/cancel_order.ts
Normal file
90
apps/sim/tools/kalshi/cancel_order.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { KalshiAuthParams, KalshiOrder } from './types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from './types'
|
||||
|
||||
export interface KalshiCancelOrderParams extends KalshiAuthParams {
|
||||
orderId: string // Order ID to cancel (required)
|
||||
}
|
||||
|
||||
export interface KalshiCancelOrderResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
order: KalshiOrder
|
||||
reducedBy: number
|
||||
metadata: {
|
||||
operation: 'cancel_order'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiCancelOrderTool: ToolConfig<KalshiCancelOrderParams, KalshiCancelOrderResponse> =
|
||||
{
|
||||
id: 'kalshi_cancel_order',
|
||||
name: 'Cancel Order on Kalshi',
|
||||
description: 'Cancel an existing order on Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The order ID to cancel',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId}`),
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId}`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'DELETE', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'cancel_order')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: data.order,
|
||||
reducedBy: data.reduced_by || 0,
|
||||
metadata: {
|
||||
operation: 'cancel_order' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Canceled order data',
|
||||
properties: {
|
||||
order: { type: 'object', description: 'The canceled order object' },
|
||||
reducedBy: { type: 'number', description: 'Number of contracts canceled' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
208
apps/sim/tools/kalshi/create_order.ts
Normal file
208
apps/sim/tools/kalshi/create_order.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { KalshiAuthParams, KalshiOrder } from './types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from './types'
|
||||
|
||||
export interface KalshiCreateOrderParams extends KalshiAuthParams {
|
||||
ticker: string // Market ticker (required)
|
||||
side: string // 'yes' or 'no' (required)
|
||||
action: string // 'buy' or 'sell' (required)
|
||||
count: string // Number of contracts (required)
|
||||
type?: string // 'limit' or 'market' (default: limit)
|
||||
yesPrice?: string // Yes price in cents (1-99)
|
||||
noPrice?: string // No price in cents (1-99)
|
||||
yesPriceDollars?: string // Yes price in dollars (e.g., "0.56")
|
||||
noPriceDollars?: string // No price in dollars (e.g., "0.56")
|
||||
clientOrderId?: string // Custom order identifier
|
||||
expirationTs?: string // Unix timestamp expiration
|
||||
timeInForce?: string // 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel'
|
||||
buyMaxCost?: string // Maximum cost in cents
|
||||
postOnly?: string // 'true' or 'false' - maker-only orders
|
||||
reduceOnly?: string // 'true' or 'false' - position reduction only
|
||||
selfTradePreventionType?: string // 'taker_at_cross' or 'maker'
|
||||
orderGroupId?: string // Associated order group
|
||||
}
|
||||
|
||||
export interface KalshiCreateOrderResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
order: KalshiOrder
|
||||
metadata: {
|
||||
operation: 'create_order'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiCreateOrderTool: ToolConfig<KalshiCreateOrderParams, KalshiCreateOrderResponse> =
|
||||
{
|
||||
id: 'kalshi_create_order',
|
||||
name: 'Create Order on Kalshi',
|
||||
description: 'Create a new order on a Kalshi prediction market',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Market ticker (e.g., KXBTC-24DEC31)',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: "Side of the order: 'yes' or 'no'",
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: "Action type: 'buy' or 'sell'",
|
||||
},
|
||||
count: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Number of contracts (minimum 1)',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: "Order type: 'limit' or 'market' (default: limit)",
|
||||
},
|
||||
yesPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Yes price in cents (1-99)',
|
||||
},
|
||||
noPrice: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'No price in cents (1-99)',
|
||||
},
|
||||
yesPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Yes price in dollars (e.g., "0.56")',
|
||||
},
|
||||
noPriceDollars: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'No price in dollars (e.g., "0.56")',
|
||||
},
|
||||
clientOrderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Custom order identifier',
|
||||
},
|
||||
expirationTs: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Unix timestamp for order expiration',
|
||||
},
|
||||
timeInForce: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: "Time in force: 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel'",
|
||||
},
|
||||
buyMaxCost: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Maximum cost in cents (auto-enables fill_or_kill)',
|
||||
},
|
||||
postOnly: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: "Set to 'true' for maker-only orders",
|
||||
},
|
||||
reduceOnly: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: "Set to 'true' for position reduction only",
|
||||
},
|
||||
selfTradePreventionType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: "Self-trade prevention: 'taker_at_cross' or 'maker'",
|
||||
},
|
||||
orderGroupId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Associated order group ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => buildKalshiUrl('/portfolio/orders'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const path = '/trade-api/v2/portfolio/orders'
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'POST', path)
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
ticker: params.ticker,
|
||||
side: params.side.toLowerCase(),
|
||||
action: params.action.toLowerCase(),
|
||||
count: Number.parseInt(params.count, 10),
|
||||
}
|
||||
|
||||
if (params.type) body.type = params.type.toLowerCase()
|
||||
if (params.yesPrice) body.yes_price = Number.parseInt(params.yesPrice, 10)
|
||||
if (params.noPrice) body.no_price = Number.parseInt(params.noPrice, 10)
|
||||
if (params.yesPriceDollars) body.yes_price_dollars = params.yesPriceDollars
|
||||
if (params.noPriceDollars) body.no_price_dollars = params.noPriceDollars
|
||||
if (params.clientOrderId) body.client_order_id = params.clientOrderId
|
||||
if (params.expirationTs) body.expiration_ts = Number.parseInt(params.expirationTs, 10)
|
||||
if (params.timeInForce) body.time_in_force = params.timeInForce
|
||||
if (params.buyMaxCost) body.buy_max_cost = Number.parseInt(params.buyMaxCost, 10)
|
||||
if (params.postOnly) body.post_only = params.postOnly === 'true'
|
||||
if (params.reduceOnly) body.reduce_only = params.reduceOnly === 'true'
|
||||
if (params.selfTradePreventionType)
|
||||
body.self_trade_prevention_type = params.selfTradePreventionType
|
||||
if (params.orderGroupId) body.order_group_id = params.orderGroupId
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'create_order')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: data.order,
|
||||
metadata: {
|
||||
operation: 'create_order' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Created order data',
|
||||
properties: {
|
||||
order: { type: 'object', description: 'The created order object' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -28,11 +28,13 @@ export const kalshiGetBalanceTool: ToolConfig<KalshiGetBalanceParams, KalshiGetB
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,9 +5,9 @@ import { buildKalshiUrl, handleKalshiError } from './types'
|
||||
export interface KalshiGetCandlesticksParams {
|
||||
seriesTicker: string
|
||||
ticker: string
|
||||
startTs?: number
|
||||
endTs?: number
|
||||
periodInterval?: number // 1, 60, or 1440 (1min, 1hour, 1day)
|
||||
startTs: number
|
||||
endTs: number
|
||||
periodInterval: number // 1, 60, or 1440 (1min, 1hour, 1day)
|
||||
}
|
||||
|
||||
export interface KalshiGetCandlesticksResponse {
|
||||
@@ -46,17 +46,17 @@ export const kalshiGetCandlesticksTool: ToolConfig<
|
||||
},
|
||||
startTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Start timestamp (Unix milliseconds)',
|
||||
required: true,
|
||||
description: 'Start timestamp (Unix seconds)',
|
||||
},
|
||||
endTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'End timestamp (Unix milliseconds)',
|
||||
required: true,
|
||||
description: 'End timestamp (Unix seconds)',
|
||||
},
|
||||
periodInterval: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
required: true,
|
||||
description: 'Period interval: 1 (1min), 60 (1hour), or 1440 (1day)',
|
||||
},
|
||||
},
|
||||
@@ -64,16 +64,15 @@ export const kalshiGetCandlesticksTool: ToolConfig<
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.startTs !== undefined) queryParams.append('start_ts', params.startTs.toString())
|
||||
if (params.endTs !== undefined) queryParams.append('end_ts', params.endTs.toString())
|
||||
if (params.periodInterval !== undefined)
|
||||
queryParams.append('period_interval', params.periodInterval.toString())
|
||||
queryParams.append('start_ts', params.startTs.toString())
|
||||
queryParams.append('end_ts', params.endTs.toString())
|
||||
queryParams.append('period_interval', params.periodInterval.toString())
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl(
|
||||
`/series/${params.seriesTicker}/markets/${params.ticker}/candlesticks`
|
||||
)
|
||||
return query ? `${url}?${query}` : url
|
||||
return `${url}?${query}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
|
||||
@@ -37,11 +37,13 @@ export const kalshiGetFillsTool: ToolConfig<KalshiGetFillsParams, KalshiGetFills
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
|
||||
86
apps/sim/tools/kalshi/get_order.ts
Normal file
86
apps/sim/tools/kalshi/get_order.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { KalshiAuthParams, KalshiOrder } from './types'
|
||||
import { buildKalshiAuthHeaders, buildKalshiUrl, handleKalshiError } from './types'
|
||||
|
||||
export interface KalshiGetOrderParams extends KalshiAuthParams {
|
||||
orderId: string // Order ID to retrieve (required)
|
||||
}
|
||||
|
||||
export interface KalshiGetOrderResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
order: KalshiOrder
|
||||
metadata: {
|
||||
operation: 'get_order'
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const kalshiGetOrderTool: ToolConfig<KalshiGetOrderParams, KalshiGetOrderResponse> = {
|
||||
id: 'kalshi_get_order',
|
||||
name: 'Get Order from Kalshi',
|
||||
description: 'Retrieve details of a specific order by ID from Kalshi',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
orderId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The order ID to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildKalshiUrl(`/portfolio/orders/${params.orderId}`),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const path = `/trade-api/v2/portfolio/orders/${params.orderId}`
|
||||
return buildKalshiAuthHeaders(params.keyId, params.privateKey, 'GET', path)
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
handleKalshiError(data, response.status, 'get_order')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
order: data.order,
|
||||
metadata: {
|
||||
operation: 'get_order' as const,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'object',
|
||||
description: 'Order data',
|
||||
properties: {
|
||||
order: { type: 'object', description: 'The order object' },
|
||||
metadata: { type: 'object', description: 'Operation metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { buildKalshiUrl, handleKalshiError } from './types'
|
||||
|
||||
export interface KalshiGetOrderbookParams {
|
||||
ticker: string
|
||||
depth?: number
|
||||
}
|
||||
|
||||
export interface KalshiGetOrderbookResponse {
|
||||
@@ -25,7 +24,7 @@ export const kalshiGetOrderbookTool: ToolConfig<
|
||||
> = {
|
||||
id: 'kalshi_get_orderbook',
|
||||
name: 'Get Market Orderbook from Kalshi',
|
||||
description: 'Retrieve the orderbook (bids and asks) for a specific market',
|
||||
description: 'Retrieve the orderbook (yes and no bids) for a specific market',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
@@ -34,22 +33,10 @@ export const kalshiGetOrderbookTool: ToolConfig<
|
||||
required: true,
|
||||
description: 'Market ticker (e.g., KXBTC-24DEC31)',
|
||||
},
|
||||
depth: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Number of price levels to return per side',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.depth !== undefined) queryParams.append('depth', params.depth.toString())
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildKalshiUrl(`/markets/${params.ticker}/orderbook`)
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
url: (params) => buildKalshiUrl(`/markets/${params.ticker}/orderbook`),
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -36,11 +36,13 @@ export const kalshiGetOrdersTool: ToolConfig<KalshiGetOrdersParams, KalshiGetOrd
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
|
||||
@@ -39,11 +39,13 @@ export const kalshiGetPositionsTool: ToolConfig<
|
||||
keyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Kalshi API Key ID',
|
||||
},
|
||||
privateKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your RSA Private Key (PEM format)',
|
||||
},
|
||||
ticker: {
|
||||
|
||||
@@ -2,11 +2,7 @@ import type { ToolConfig } from '@/tools/types'
|
||||
import type { KalshiPaginationParams, KalshiPagingInfo, KalshiTrade } from './types'
|
||||
import { buildKalshiUrl, handleKalshiError } from './types'
|
||||
|
||||
export interface KalshiGetTradesParams extends KalshiPaginationParams {
|
||||
ticker?: string
|
||||
minTs?: number
|
||||
maxTs?: number
|
||||
}
|
||||
export interface KalshiGetTradesParams extends KalshiPaginationParams {}
|
||||
|
||||
export interface KalshiGetTradesResponse {
|
||||
success: boolean
|
||||
@@ -24,25 +20,10 @@ export interface KalshiGetTradesResponse {
|
||||
export const kalshiGetTradesTool: ToolConfig<KalshiGetTradesParams, KalshiGetTradesResponse> = {
|
||||
id: 'kalshi_get_trades',
|
||||
name: 'Get Trades from Kalshi',
|
||||
description: 'Retrieve recent trades across all markets or for a specific market',
|
||||
description: 'Retrieve recent trades across all markets',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Filter by market ticker',
|
||||
},
|
||||
minTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Minimum timestamp (Unix milliseconds)',
|
||||
},
|
||||
maxTs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Maximum timestamp (Unix milliseconds)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
@@ -58,9 +39,6 @@ export const kalshiGetTradesTool: ToolConfig<KalshiGetTradesParams, KalshiGetTra
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.ticker) queryParams.append('ticker', params.ticker)
|
||||
if (params.minTs !== undefined) queryParams.append('min_ts', params.minTs.toString())
|
||||
if (params.maxTs !== undefined) queryParams.append('max_ts', params.maxTs.toString())
|
||||
if (params.limit) queryParams.append('limit', params.limit)
|
||||
if (params.cursor) queryParams.append('cursor', params.cursor)
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export { kalshiAmendOrderTool } from './amend_order'
|
||||
export { kalshiCancelOrderTool } from './cancel_order'
|
||||
export { kalshiCreateOrderTool } from './create_order'
|
||||
export { kalshiGetBalanceTool } from './get_balance'
|
||||
export { kalshiGetCandlesticksTool } from './get_candlesticks'
|
||||
export { kalshiGetEventTool } from './get_event'
|
||||
@@ -6,6 +9,7 @@ export { kalshiGetExchangeStatusTool } from './get_exchange_status'
|
||||
export { kalshiGetFillsTool } from './get_fills'
|
||||
export { kalshiGetMarketTool } from './get_market'
|
||||
export { kalshiGetMarketsTool } from './get_markets'
|
||||
export { kalshiGetOrderTool } from './get_order'
|
||||
export { kalshiGetOrderbookTool } from './get_orderbook'
|
||||
export { kalshiGetOrdersTool } from './get_orders'
|
||||
export { kalshiGetPositionsTool } from './get_positions'
|
||||
|
||||
@@ -4,7 +4,7 @@ import { buildGammaUrl, handlePolymarketError } from './types'
|
||||
|
||||
export interface PolymarketGetEventsParams extends PolymarketPaginationParams {
|
||||
closed?: string // 'true' or 'false' - filter for closed/active events
|
||||
order?: string // sort field
|
||||
order?: string // sort field (e.g., 'volume', 'liquidity', 'startDate', 'endDate')
|
||||
ascending?: string // 'true' or 'false' - sort direction
|
||||
tagId?: string // filter by tag ID
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export const polymarketGetEventsTool: ToolConfig<
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Sort field (e.g., id, volume)',
|
||||
description: 'Sort field (e.g., volume, liquidity, startDate, endDate, createdAt)',
|
||||
},
|
||||
ascending: {
|
||||
type: 'string',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { buildGammaUrl, handlePolymarketError } from './types'
|
||||
|
||||
export interface PolymarketGetMarketsParams extends PolymarketPaginationParams {
|
||||
closed?: string // 'true' or 'false' - filter for closed/active markets
|
||||
order?: string // sort field (e.g., 'id', 'volume', 'liquidity')
|
||||
order?: string // sort field - use camelCase (e.g., 'volumeNum', 'liquidityNum', 'startDate', 'endDate')
|
||||
ascending?: string // 'true' or 'false' - sort direction
|
||||
tagId?: string // filter by tag ID
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export const polymarketGetMarketsTool: ToolConfig<
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Sort field (e.g., id, volume, liquidity)',
|
||||
description: 'Sort field (e.g., volumeNum, liquidityNum, startDate, endDate, createdAt)',
|
||||
},
|
||||
ascending: {
|
||||
type: 'string',
|
||||
|
||||
@@ -295,6 +295,15 @@ import {
|
||||
googleSheetsUpdateTool,
|
||||
googleSheetsWriteTool,
|
||||
} from '@/tools/google_sheets'
|
||||
import {
|
||||
googleSlidesAddImageTool,
|
||||
googleSlidesAddSlideTool,
|
||||
googleSlidesCreateTool,
|
||||
googleSlidesGetThumbnailTool,
|
||||
googleSlidesReadTool,
|
||||
googleSlidesReplaceAllTextTool,
|
||||
googleSlidesWriteTool,
|
||||
} from '@/tools/google_slides'
|
||||
import {
|
||||
createMattersExportTool,
|
||||
createMattersHoldsTool,
|
||||
@@ -441,6 +450,9 @@ import {
|
||||
jiraWriteTool,
|
||||
} from '@/tools/jira'
|
||||
import {
|
||||
kalshiAmendOrderTool,
|
||||
kalshiCancelOrderTool,
|
||||
kalshiCreateOrderTool,
|
||||
kalshiGetBalanceTool,
|
||||
kalshiGetCandlesticksTool,
|
||||
kalshiGetEventsTool,
|
||||
@@ -451,6 +463,7 @@ import {
|
||||
kalshiGetMarketTool,
|
||||
kalshiGetOrderbookTool,
|
||||
kalshiGetOrdersTool,
|
||||
kalshiGetOrderTool,
|
||||
kalshiGetPositionsTool,
|
||||
kalshiGetSeriesByTickerTool,
|
||||
kalshiGetTradesTool,
|
||||
@@ -1376,12 +1389,16 @@ export const tools: Record<string, ToolConfig> = {
|
||||
kalshi_get_balance: kalshiGetBalanceTool,
|
||||
kalshi_get_positions: kalshiGetPositionsTool,
|
||||
kalshi_get_orders: kalshiGetOrdersTool,
|
||||
kalshi_get_order: kalshiGetOrderTool,
|
||||
kalshi_get_orderbook: kalshiGetOrderbookTool,
|
||||
kalshi_get_trades: kalshiGetTradesTool,
|
||||
kalshi_get_candlesticks: kalshiGetCandlesticksTool,
|
||||
kalshi_get_fills: kalshiGetFillsTool,
|
||||
kalshi_get_series_by_ticker: kalshiGetSeriesByTickerTool,
|
||||
kalshi_get_exchange_status: kalshiGetExchangeStatusTool,
|
||||
kalshi_create_order: kalshiCreateOrderTool,
|
||||
kalshi_cancel_order: kalshiCancelOrderTool,
|
||||
kalshi_amend_order: kalshiAmendOrderTool,
|
||||
polymarket_get_markets: polymarketGetMarketsTool,
|
||||
polymarket_get_market: polymarketGetMarketTool,
|
||||
polymarket_get_events: polymarketGetEventsTool,
|
||||
@@ -1679,6 +1696,13 @@ export const tools: Record<string, ToolConfig> = {
|
||||
google_sheets_write: googleSheetsWriteTool,
|
||||
google_sheets_update: googleSheetsUpdateTool,
|
||||
google_sheets_append: googleSheetsAppendTool,
|
||||
google_slides_read: googleSlidesReadTool,
|
||||
google_slides_write: googleSlidesWriteTool,
|
||||
google_slides_create: googleSlidesCreateTool,
|
||||
google_slides_replace_all_text: googleSlidesReplaceAllTextTool,
|
||||
google_slides_add_slide: googleSlidesAddSlideTool,
|
||||
google_slides_get_thumbnail: googleSlidesGetThumbnailTool,
|
||||
google_slides_add_image: googleSlidesAddImageTool,
|
||||
perplexity_chat: perplexityChatTool,
|
||||
perplexity_search: perplexitySearchTool,
|
||||
posthog_capture_event: posthogCaptureEventTool,
|
||||
|
||||
7
bun.lock
7
bun.lock
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "simstudio",
|
||||
@@ -113,6 +114,7 @@
|
||||
"@react-email/components": "^0.0.34",
|
||||
"@react-email/render": "2.0.0",
|
||||
"@trigger.dev/sdk": "4.1.2",
|
||||
"@types/react-window": "2.0.0",
|
||||
"@types/three": "0.177.0",
|
||||
"better-auth": "1.3.12",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
@@ -160,6 +162,7 @@
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"react-window": "2.2.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
@@ -1423,6 +1426,8 @@
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
"@types/react-window": ["@types/react-window@2.0.0", "", { "dependencies": { "react-window": "*" } }, "sha512-E8hMDtImEpMk1SjswSvqoSmYvk7GEtyVaTa/GJV++FdDNuMVVEzpAClyJ0nqeKYBrMkGiyH6M1+rPLM0Nu1exQ=="],
|
||||
|
||||
"@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="],
|
||||
|
||||
"@types/ssh2": ["@types/ssh2@1.15.5", "", { "dependencies": { "@types/node": "^18.11.18" } }, "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ=="],
|
||||
@@ -2809,6 +2814,8 @@
|
||||
|
||||
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
||||
|
||||
"react-window": ["react-window@2.2.3", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-gTRqQYC8ojbiXyd9duYFiSn2TJw0ROXCgYjenOvNKITWzK0m0eCvkUsEUM08xvydkMh7ncp+LE0uS3DeNGZxnQ=="],
|
||||
|
||||
"reactflow": ["reactflow@11.11.4", "", { "dependencies": { "@reactflow/background": "11.3.14", "@reactflow/controls": "11.2.14", "@reactflow/core": "11.11.4", "@reactflow/minimap": "11.7.14", "@reactflow/node-resizer": "2.2.14", "@reactflow/node-toolbar": "1.3.14" }, "peerDependencies": { "react": ">=17", "react-dom": ">=17" } }, "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og=="],
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
|
||||
Reference in New Issue
Block a user