-
Notifications
You must be signed in to change notification settings - Fork 371
feat: add StudioByAI21Labs integration #918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
13ad083
5889b8b
9751602
71b04e7
4666f8d
ca34eee
df71e53
d9e201d
ef63eb6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import type { ApiRequestOptions, OpenAPIConfig } from 'corsair/http'; | ||
| import { request } from 'corsair/http'; | ||
|
|
||
| export class StudioByAI21LabsAPIError extends Error { | ||
| constructor( | ||
| message: string, | ||
| public readonly code?: string, | ||
| ) { | ||
| super(message); | ||
| this.name = 'StudioByAI21LabsAPIError'; | ||
| } | ||
| } | ||
|
|
||
| // TODO: Update with your API base URL | ||
| const STUDIOBYAI21LABS_API_BASE = 'https://api.ai21.com/studio/v1'; | ||
|
|
||
| export async function makeStudioByAI21LabsRequest<T>( | ||
| endpoint: string, | ||
| apiKey: string, | ||
| options: { | ||
| method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; | ||
| body?: Record<string, unknown>; | ||
| query?: Record<string, string | number | boolean | undefined>; | ||
| } = {}, | ||
| ): Promise<T> { | ||
| const { method = 'GET', body, query } = options; | ||
|
|
||
| const config: OpenAPIConfig = { | ||
| BASE: STUDIOBYAI21LABS_API_BASE, | ||
| VERSION: '1.0.0', | ||
| WITH_CREDENTIALS: false, | ||
| CREDENTIALS: 'omit', | ||
| TOKEN: apiKey, | ||
| HEADERS: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${apiKey}`, | ||
| }, | ||
| }; | ||
|
|
||
| const requestOptions: ApiRequestOptions = { | ||
| method, | ||
| url: endpoint, | ||
| body: | ||
| method === 'POST' || method === 'PUT' || method === 'PATCH' | ||
| ? body | ||
| : undefined, | ||
| mediaType: 'application/json; charset=utf-8', | ||
| query: method === 'GET' ? query : undefined, | ||
| }; | ||
|
|
||
| try { | ||
| return await request<T>(config, requestOptions); | ||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| throw new StudioByAI21LabsAPIError(error.message); | ||
| } | ||
| throw new StudioByAI21LabsAPIError('Unknown error'); | ||
| } | ||
| } | ||
|
|
||
| const buildUrl = (endpoint: string): string => { | ||
| const baseUrl = STUDIOBYAI21LABS_API_BASE.endsWith('/') | ||
| ? STUDIOBYAI21LABS_API_BASE.slice(0, -1) | ||
| : STUDIOBYAI21LABS_API_BASE; | ||
| const path = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint; | ||
| return `${baseUrl}/${path}`; | ||
| }; | ||
|
|
||
| export async function uploadStudioByAI21LabsFile<T>( | ||
| endpoint: string, | ||
| apiKey: string, | ||
| options: { | ||
| file: Blob | string; | ||
| fileName: string; | ||
| fields?: Record<string, string | undefined>; | ||
| }, | ||
| ): Promise<T> { | ||
| const { file, fileName, fields = {} } = options; | ||
| const blob = typeof file === 'string' ? new Blob([file]) : file; | ||
|
|
||
| const formData = new FormData(); | ||
| formData.append('file', blob, fileName); | ||
| for (const [key, value] of Object.entries(fields)) { | ||
| if (value !== undefined) formData.append(key, value); | ||
| } | ||
|
|
||
| const response = await fetch(buildUrl(endpoint), { | ||
| method: 'POST', | ||
| headers: { Authorization: `Bearer ${apiKey}` }, | ||
| body: formData, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const text = await response.text(); | ||
| throw new StudioByAI21LabsAPIError( | ||
| `Upload failed: status ${response.status}; body: ${text}`, | ||
| undefined, | ||
| ); | ||
| } | ||
|
|
||
| return response.json() as Promise<T>; | ||
| } | ||
|
|
||
| export async function downloadStudioByAI21LabsFile( | ||
| endpoint: string, | ||
| apiKey: string, | ||
| ): Promise<string> { | ||
| const response = await fetch(buildUrl(endpoint), { | ||
| method: 'GET', | ||
| headers: { Authorization: `Bearer ${apiKey}` }, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const text = await response.text(); | ||
| throw new StudioByAI21LabsAPIError( | ||
| `Download failed: status ${response.status}; body: ${text}`, | ||
| undefined, | ||
| ); | ||
| } | ||
|
|
||
| return response.text(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| import { makeStudioByAI21LabsRequest } from '../client'; | ||
| import type { StudioByAI21LabsEndpoints } from '../index'; | ||
|
|
||
| export const createAssistant: StudioByAI21LabsEndpoints['createAssistant'] = | ||
| async (ctx, input) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'POST', | ||
| 'assistants', | ||
| input, | ||
| ); | ||
|
greptile-apps[bot] marked this conversation as resolved.
Outdated
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline packages/studiobyai21labs/endpoints/assistants.ts --items all
ast-grep run \
--lang ts \
--pattern 'makeStudioByAI21LabsRequest($A, $B, $C, $D)' \
packages/studiobyai21labs/endpoints/assistants.tsRepository: corsairdev/corsair Length of output: 7325 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- client outline ---'
ast-grep outline packages/studiobyai21labs/client.ts --items all
echo '--- client source ---'
cat -n packages/studiobyai21labs/client.ts
echo '--- endpoint package files ---'
git ls-files packages/studiobyai21labs
echo '--- helper declarations and usages ---'
rg -n -C 3 'makeStudioByAI21LabsRequest|StudioByAI21LabsContext|StudioByAI21LabsEndpoints' packages/studiobyai21labs
echo '--- assistants source ---'
cat -n packages/studiobyai21labs/endpoints/assistants.tsRepository: corsairdev/corsair Length of output: 50374 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- assistants.ts ---'
cat -n packages/studiobyai21labs/endpoints/assistants.ts
echo '--- assistant endpoint declarations ---'
rg -n -C 5 'createAssistant|createAssistantPlan|createAssistantRoute|deleteAssistant|deleteAssistantRoute|getAssistant|getAssistantRoute|getAssistantsByMcp|getPlan|listAssistants|listPlans|modifyAssistant|modifyAssistantPlan|modifyAssistantRoute|runAssistant|validatePlan' packages/studiobyai21labs/index.ts packages/studiobyai21labs/endpoints/types.ts
echo '--- focused call-shape verifier ---'
python3 - <<'PY'
import re
from pathlib import Path
source = Path("packages/studiobyai21labs/endpoints/assistants.ts").read_text()
calls = re.findall(
r"makeStudioByAI21LabsRequest\(\s*(\w+),\s*'([^']+)',\s*([^,\n]+),\s*([^)\n]+)\s*\)",
source,
re.S,
)
print(f"four_argument_calls={len(calls)}")
for i, (ctx, method, endpoint, payload) in enumerate(calls, 1):
print(f"{i}: ctx={ctx}; method={method}; endpoint={endpoint.strip()}; payload={payload.strip()}")
PYRepository: corsairdev/corsair Length of output: 46210 Update all assistant endpoint calls to use Pass request payloads as 🤖 Prompt for AI Agents |
||
| return response as any; | ||
| }; | ||
|
|
||
| export const createAssistantPlan: StudioByAI21LabsEndpoints['createAssistantPlan'] = | ||
| async (ctx, input) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'POST', | ||
| 'assistant-plans', | ||
| input, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const createAssistantRoute: StudioByAI21LabsEndpoints['createAssistantRoute'] = | ||
| async (ctx, input) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'POST', | ||
| 'assistant-routes', | ||
| input, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const deleteAssistant: StudioByAI21LabsEndpoints['deleteAssistant'] = | ||
| async (ctx, input) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'DELETE', | ||
| `assistants/${input.id}`, | ||
| input, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const deleteAssistantRoute: StudioByAI21LabsEndpoints['deleteAssistantRoute'] = | ||
| async (ctx, input) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'DELETE', | ||
| `assistant-routes/${input.id}`, | ||
| input, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const getAssistant: StudioByAI21LabsEndpoints['getAssistant'] = async ( | ||
| ctx, | ||
| input, | ||
| ) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'GET', | ||
| `assistants/${input.id}`, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const getAssistantRoute: StudioByAI21LabsEndpoints['getAssistantRoute'] = | ||
| async (ctx, input) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'GET', | ||
| `assistant-routes/${input.id}`, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const getAssistantsByMcp: StudioByAI21LabsEndpoints['getAssistantsByMcp'] = | ||
| async (ctx, input) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'GET', | ||
| 'assistants/mcp', | ||
| { mcpId: input.mcpId }, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const getPlan: StudioByAI21LabsEndpoints['getPlan'] = async ( | ||
| ctx, | ||
| input, | ||
| ) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'GET', | ||
| `assistant-plans/${input.id}`, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const listAssistants: StudioByAI21LabsEndpoints['listAssistants'] = | ||
| async (ctx, input) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'GET', | ||
| 'assistants', | ||
| input, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const listPlans: StudioByAI21LabsEndpoints['listPlans'] = async ( | ||
| ctx, | ||
| input, | ||
| ) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'GET', | ||
| 'assistant-plans', | ||
| input, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const modifyAssistant: StudioByAI21LabsEndpoints['modifyAssistant'] = | ||
| async (ctx, input) => { | ||
| const { id, ...body } = input; | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'PUT', | ||
| `assistants/${id}`, | ||
| body, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const modifyAssistantPlan: StudioByAI21LabsEndpoints['modifyAssistantPlan'] = | ||
| async (ctx, input) => { | ||
| const { id, ...body } = input; | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'PUT', | ||
| `assistant-plans/${id}`, | ||
| body, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const modifyAssistantRoute: StudioByAI21LabsEndpoints['modifyAssistantRoute'] = | ||
| async (ctx, input) => { | ||
| const { id, ...body } = input; | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'PUT', | ||
| `assistant-routes/${id}`, | ||
| body, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const runAssistant: StudioByAI21LabsEndpoints['runAssistant'] = async ( | ||
| ctx, | ||
| input, | ||
| ) => { | ||
| const { id, ...body } = input; | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'POST', | ||
| `assistants/${id}/run`, | ||
| body, | ||
| ); | ||
| return response as any; | ||
| }; | ||
|
|
||
| export const validatePlan: StudioByAI21LabsEndpoints['validatePlan'] = async ( | ||
| ctx, | ||
| input, | ||
| ) => { | ||
| const response = await makeStudioByAI21LabsRequest( | ||
| ctx, | ||
| 'POST', | ||
| 'assistant-plans/validate', | ||
| input, | ||
| ); | ||
| return response as any; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||
| import { logEventFromContext } from 'corsair/core'; | ||||||
| import type { StudioByAI21LabsEndpoints } from '..'; | ||||||
| import { makeStudioByAI21LabsRequest } from '../client'; | ||||||
| import type { StudioByAI21LabsEndpointOutputs } from './types'; | ||||||
|
|
||||||
| export const completions: StudioByAI21LabsEndpoints['chatCompletions'] = async ( | ||||||
| ctx, | ||||||
| input, | ||||||
| ) => { | ||||||
| const response = await makeStudioByAI21LabsRequest< | ||||||
| StudioByAI21LabsEndpointOutputs['chatCompletions'] | ||||||
| >('chat/completions', ctx.key, { method: 'POST', body: input }); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Route this model call through the LiteLLM gateway. This handler sends chat completions to the direct provider client. Route model calls through As per coding guidelines, “Route model calls through 🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||
|
|
||||||
| await logEventFromContext( | ||||||
| ctx, | ||||||
| 'studiobyai21labs.chat.completions', | ||||||
| { ...input }, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a caller sends private prompt or user content, spreading the complete input into
Suggested change
|
||||||
| 'completed', | ||||||
| ); | ||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||
| return response; | ||||||
| }; | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The plugin still exports generated example endpoint and webhook operations while transport, webhook matching, tenant matching, and schema files retain unresolved placeholder or TODO behavior. Consumers consequently receive nonexistent example operations and incomplete webhook handling instead of a production-ready provider surface.
Rule Used: Flag boilerplate residue from the plugin generator... (source)
Knowledge Base Used: The provider-plugin package pattern
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!