kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
Merge pull request #719 from transitive-bullshit/feature/rework-api-keys
commit
dd49abc20a
|
@ -10,18 +10,20 @@ import {
|
||||||
openapiErrorResponses
|
openapiErrorResponses
|
||||||
} from '@/lib/openapi-utils'
|
} from '@/lib/openapi-utils'
|
||||||
|
|
||||||
import { consumerTokenParamsSchema, populateConsumerSchema } from './schemas'
|
import { consumerApiKeyParamsSchema, populateConsumerSchema } from './schemas'
|
||||||
import { setAdminCacheControlForConsumer } from './utils'
|
import { setAdminCacheControlForConsumer } from './utils'
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
description: 'Gets a consumer by API token. This route is admin-only.',
|
description: 'Gets a consumer by API key. This route is admin-only.',
|
||||||
tags: ['admin', 'consumers'],
|
tags: ['admin', 'consumers'],
|
||||||
operationId: 'adminGetConsumerByToken',
|
operationId: 'adminGetConsumerByApiKey',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: 'admin/consumers/tokens/{token}',
|
// TODO: is it wise to use a path param for the API key? especially wehn it'll
|
||||||
|
// be cached in cloudflare's shared cache?
|
||||||
|
path: 'admin/consumers/api-keys/{apiKey}',
|
||||||
security: openapiAuthenticatedSecuritySchemas,
|
security: openapiAuthenticatedSecuritySchemas,
|
||||||
request: {
|
request: {
|
||||||
params: consumerTokenParamsSchema,
|
params: consumerApiKeyParamsSchema,
|
||||||
query: populateConsumerSchema
|
query: populateConsumerSchema
|
||||||
},
|
},
|
||||||
responses: {
|
responses: {
|
||||||
|
@ -38,21 +40,21 @@ const route = createRoute({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function registerV1AdminGetConsumerByToken(
|
export function registerV1AdminGetConsumerByApiKey(
|
||||||
app: OpenAPIHono<AuthenticatedHonoEnv>
|
app: OpenAPIHono<AuthenticatedHonoEnv>
|
||||||
) {
|
) {
|
||||||
return app.openapi(route, async (c) => {
|
return app.openapi(route, async (c) => {
|
||||||
const { token } = c.req.valid('param')
|
const { apiKey } = c.req.valid('param')
|
||||||
const { populate = [] } = c.req.valid('query')
|
const { populate = [] } = c.req.valid('query')
|
||||||
await aclAdmin(c)
|
await aclAdmin(c)
|
||||||
|
|
||||||
const consumer = await db.query.consumers.findFirst({
|
const consumer = await db.query.consumers.findFirst({
|
||||||
where: eq(schema.consumers.token, token),
|
where: eq(schema.consumers.token, apiKey),
|
||||||
with: {
|
with: {
|
||||||
...Object.fromEntries(populate.map((field) => [field, true]))
|
...Object.fromEntries(populate.map((field) => [field, true]))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
assert(consumer, 404, `API token not found "${token}"`)
|
assert(consumer, 404, `API key not found "${apiKey}"`)
|
||||||
|
|
||||||
setAdminCacheControlForConsumer(c, consumer)
|
setAdminCacheControlForConsumer(c, consumer)
|
||||||
return c.json(parseZodSchema(schema.consumerAdminSelectSchema, consumer))
|
return c.json(parseZodSchema(schema.consumerAdminSelectSchema, consumer))
|
|
@ -4,7 +4,7 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
|
||||||
import type { AuthenticatedHonoEnv } from '@/lib/types'
|
import type { AuthenticatedHonoEnv } from '@/lib/types'
|
||||||
import { db, eq, schema } from '@/db'
|
import { db, eq, schema } from '@/db'
|
||||||
import { acl } from '@/lib/acl'
|
import { acl } from '@/lib/acl'
|
||||||
import { createConsumerToken } from '@/lib/create-consumer-token'
|
import { createConsumerApiKey } from '@/lib/create-consumer-api-key'
|
||||||
import {
|
import {
|
||||||
openapiAuthenticatedSecuritySchemas,
|
openapiAuthenticatedSecuritySchemas,
|
||||||
openapiErrorResponse404,
|
openapiErrorResponse404,
|
||||||
|
@ -14,11 +14,11 @@ import {
|
||||||
import { consumerIdParamsSchema } from './schemas'
|
import { consumerIdParamsSchema } from './schemas'
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
description: "Refreshes a consumer's API token.",
|
description: "Refreshes a consumer's API key.",
|
||||||
tags: ['consumers'],
|
tags: ['consumers'],
|
||||||
operationId: 'refreshConsumerToken',
|
operationId: 'refreshConsumerApiKey',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
path: 'consumers/{consumerId}/refresh-token',
|
path: 'consumers/{consumerId}/refresh-api-key',
|
||||||
security: openapiAuthenticatedSecuritySchemas,
|
security: openapiAuthenticatedSecuritySchemas,
|
||||||
request: {
|
request: {
|
||||||
params: consumerIdParamsSchema
|
params: consumerIdParamsSchema
|
||||||
|
@ -37,7 +37,7 @@ const route = createRoute({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function registerV1RefreshConsumerToken(
|
export function registerV1RefreshConsumerApiKey(
|
||||||
app: OpenAPIHono<AuthenticatedHonoEnv>
|
app: OpenAPIHono<AuthenticatedHonoEnv>
|
||||||
) {
|
) {
|
||||||
return app.openapi(route, async (c) => {
|
return app.openapi(route, async (c) => {
|
||||||
|
@ -53,7 +53,7 @@ export function registerV1RefreshConsumerToken(
|
||||||
;[consumer] = await db
|
;[consumer] = await db
|
||||||
.update(schema.consumers)
|
.update(schema.consumers)
|
||||||
.set({
|
.set({
|
||||||
token: await createConsumerToken()
|
token: await createConsumerApiKey()
|
||||||
})
|
})
|
||||||
.where(eq(schema.consumers.id, consumer.id))
|
.where(eq(schema.consumers.id, consumer.id))
|
||||||
.returning()
|
.returning()
|
|
@ -17,14 +17,14 @@ export const consumerIdParamsSchema = z.object({
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export const consumerTokenParamsSchema = z.object({
|
export const consumerApiKeyParamsSchema = z.object({
|
||||||
token: z
|
apiKey: z
|
||||||
.string()
|
.string()
|
||||||
.nonempty()
|
.nonempty()
|
||||||
.openapi({
|
.openapi({
|
||||||
param: {
|
param: {
|
||||||
description: 'Consumer token',
|
description: 'Consumer API key',
|
||||||
name: 'token',
|
name: 'apiKey',
|
||||||
in: 'path'
|
in: 'path'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { registerV1GitHubOAuthInitFlow } from './auth/github-init'
|
||||||
import { registerV1SignInWithPassword } from './auth/sign-in-with-password'
|
import { registerV1SignInWithPassword } from './auth/sign-in-with-password'
|
||||||
import { registerV1SignUpWithPassword } from './auth/sign-up-with-password'
|
import { registerV1SignUpWithPassword } from './auth/sign-up-with-password'
|
||||||
import { registerV1AdminActivateConsumer } from './consumers/admin-activate-consumer'
|
import { registerV1AdminActivateConsumer } from './consumers/admin-activate-consumer'
|
||||||
import { registerV1AdminGetConsumerByToken } from './consumers/admin-get-consumer-by-token'
|
import { registerV1AdminGetConsumerByApiKey } from './consumers/admin-get-consumer-by-api-key'
|
||||||
import { registerV1CreateBillingPortalSession } from './consumers/create-billing-portal-session'
|
import { registerV1CreateBillingPortalSession } from './consumers/create-billing-portal-session'
|
||||||
import { registerV1CreateConsumer } from './consumers/create-consumer'
|
import { registerV1CreateConsumer } from './consumers/create-consumer'
|
||||||
import { registerV1CreateConsumerBillingPortalSession } from './consumers/create-consumer-billing-portal-session'
|
import { registerV1CreateConsumerBillingPortalSession } from './consumers/create-consumer-billing-portal-session'
|
||||||
|
@ -20,7 +20,7 @@ import { registerV1GetConsumer } from './consumers/get-consumer'
|
||||||
import { registerV1GetConsumerByProjectIdentifier } from './consumers/get-consumer-by-project-identifier'
|
import { registerV1GetConsumerByProjectIdentifier } from './consumers/get-consumer-by-project-identifier'
|
||||||
import { registerV1ListConsumers } from './consumers/list-consumers'
|
import { registerV1ListConsumers } from './consumers/list-consumers'
|
||||||
import { registerV1ListConsumersForProject } from './consumers/list-project-consumers'
|
import { registerV1ListConsumersForProject } from './consumers/list-project-consumers'
|
||||||
import { registerV1RefreshConsumerToken } from './consumers/refresh-consumer-token'
|
import { registerV1RefreshConsumerApiKey } from './consumers/refresh-consumer-api-key'
|
||||||
import { registerV1UpdateConsumer } from './consumers/update-consumer'
|
import { registerV1UpdateConsumer } from './consumers/update-consumer'
|
||||||
import { registerV1AdminGetDeploymentByIdentifier } from './deployments/admin-get-deployment-by-identifier'
|
import { registerV1AdminGetDeploymentByIdentifier } from './deployments/admin-get-deployment-by-identifier'
|
||||||
import { registerV1CreateDeployment } from './deployments/create-deployment'
|
import { registerV1CreateDeployment } from './deployments/create-deployment'
|
||||||
|
@ -119,7 +119,7 @@ registerV1CreateConsumer(privateRouter)
|
||||||
registerV1CreateConsumerCheckoutSession(privateRouter)
|
registerV1CreateConsumerCheckoutSession(privateRouter)
|
||||||
registerV1CreateConsumerBillingPortalSession(privateRouter)
|
registerV1CreateConsumerBillingPortalSession(privateRouter)
|
||||||
registerV1UpdateConsumer(privateRouter)
|
registerV1UpdateConsumer(privateRouter)
|
||||||
registerV1RefreshConsumerToken(privateRouter)
|
registerV1RefreshConsumerApiKey(privateRouter)
|
||||||
registerV1ListConsumers(privateRouter)
|
registerV1ListConsumers(privateRouter)
|
||||||
registerV1ListConsumersForProject(privateRouter)
|
registerV1ListConsumersForProject(privateRouter)
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ registerV1ListDeployments(privateRouter)
|
||||||
registerV1PublishDeployment(privateRouter)
|
registerV1PublishDeployment(privateRouter)
|
||||||
|
|
||||||
// Internal admin routes
|
// Internal admin routes
|
||||||
registerV1AdminGetConsumerByToken(privateRouter)
|
registerV1AdminGetConsumerByApiKey(privateRouter)
|
||||||
registerV1AdminActivateConsumer(privateRouter)
|
registerV1AdminActivateConsumer(privateRouter)
|
||||||
registerV1AdminGetDeploymentByIdentifier(privateRouter)
|
registerV1AdminGetDeploymentByIdentifier(privateRouter)
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,8 @@ export const consumers = pgTable(
|
||||||
...consumerPrimaryId,
|
...consumerPrimaryId,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
|
|
||||||
// API token for this consumer
|
// API key for this consumer
|
||||||
|
// (called "token" for backwards compatibility)
|
||||||
token: text().notNull(),
|
token: text().notNull(),
|
||||||
|
|
||||||
// The slug of the PricingPlan in the target deployment that this consumer
|
// The slug of the PricingPlan in the target deployment that this consumer
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { acl } from '@/lib/acl'
|
||||||
import { upsertStripeConnectCustomer } from '@/lib/billing/upsert-stripe-connect-customer'
|
import { upsertStripeConnectCustomer } from '@/lib/billing/upsert-stripe-connect-customer'
|
||||||
import { upsertStripeCustomer } from '@/lib/billing/upsert-stripe-customer'
|
import { upsertStripeCustomer } from '@/lib/billing/upsert-stripe-customer'
|
||||||
import { upsertStripePricingResources } from '@/lib/billing/upsert-stripe-pricing-resources'
|
import { upsertStripePricingResources } from '@/lib/billing/upsert-stripe-pricing-resources'
|
||||||
import { createConsumerToken } from '@/lib/create-consumer-token'
|
import { createConsumerApiKey } from '@/lib/create-consumer-api-key'
|
||||||
|
|
||||||
import { aclPublicProject } from '../acl-public-project'
|
import { aclPublicProject } from '../acl-public-project'
|
||||||
import { createStripeCheckoutSession } from '../billing/create-stripe-checkout-session'
|
import { createStripeCheckoutSession } from '../billing/create-stripe-checkout-session'
|
||||||
|
@ -174,7 +174,7 @@ export async function upsertConsumerStripeCheckout(
|
||||||
userId,
|
userId,
|
||||||
projectId,
|
projectId,
|
||||||
deploymentId,
|
deploymentId,
|
||||||
token: await createConsumerToken(),
|
token: await createConsumerApiKey(),
|
||||||
_stripeCustomerId: stripeCustomer.id
|
_stripeCustomerId: stripeCustomer.id
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { upsertStripeConnectCustomer } from '@/lib/billing/upsert-stripe-connect
|
||||||
import { upsertStripeCustomer } from '@/lib/billing/upsert-stripe-customer'
|
import { upsertStripeCustomer } from '@/lib/billing/upsert-stripe-customer'
|
||||||
import { upsertStripePricingResources } from '@/lib/billing/upsert-stripe-pricing-resources'
|
import { upsertStripePricingResources } from '@/lib/billing/upsert-stripe-pricing-resources'
|
||||||
import { upsertStripeSubscription } from '@/lib/billing/upsert-stripe-subscription'
|
import { upsertStripeSubscription } from '@/lib/billing/upsert-stripe-subscription'
|
||||||
import { createConsumerToken } from '@/lib/create-consumer-token'
|
import { createConsumerApiKey } from '@/lib/create-consumer-api-key'
|
||||||
|
|
||||||
import { aclPublicProject } from '../acl-public-project'
|
import { aclPublicProject } from '../acl-public-project'
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ export async function upsertConsumer(
|
||||||
userId,
|
userId,
|
||||||
projectId,
|
projectId,
|
||||||
deploymentId,
|
deploymentId,
|
||||||
token: await createConsumerToken(),
|
token: await createConsumerApiKey(),
|
||||||
_stripeCustomerId: stripeCustomer.id
|
_stripeCustomerId: stripeCustomer.id
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { sha256 } from '@agentic/platform-core'
|
||||||
|
|
||||||
|
export async function createConsumerApiKey(): Promise<string> {
|
||||||
|
return `sk-${sha256()}`
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
import { sha256 } from '@agentic/platform-core'
|
|
||||||
|
|
||||||
export async function createConsumerToken(): Promise<string> {
|
|
||||||
return sha256()
|
|
||||||
}
|
|
|
@ -1,17 +1,32 @@
|
||||||
import { assert } from '@agentic/platform-core'
|
import { assert, HttpError } from '@agentic/platform-core'
|
||||||
|
|
||||||
import type { AdminConsumer, GatewayHonoContext } from './types'
|
import type { AdminConsumer, GatewayHonoContext } from './types'
|
||||||
|
|
||||||
export async function getAdminConsumer(
|
export async function getAdminConsumer(
|
||||||
ctx: GatewayHonoContext,
|
ctx: GatewayHonoContext,
|
||||||
token: string
|
apiKey: string
|
||||||
): Promise<AdminConsumer> {
|
): Promise<AdminConsumer> {
|
||||||
const client = ctx.get('client')
|
const client = ctx.get('client')
|
||||||
const consumer = await client.adminGetConsumerByToken({
|
let consumer: AdminConsumer | undefined
|
||||||
token,
|
|
||||||
populate: ['user']
|
|
||||||
})
|
|
||||||
assert(consumer, 404, `API token not found "${token}"`)
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
consumer = await client.adminGetConsumerByApiKey({
|
||||||
|
apiKey,
|
||||||
|
populate: ['user']
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.response?.status === 404) {
|
||||||
|
// Hide the underlying error message from the client
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `API key not found "${apiKey}"`,
|
||||||
|
cause: err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(consumer, 404, `API key not found "${apiKey}"`)
|
||||||
return consumer
|
return consumer
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,13 @@ export function ExampleUsage({
|
||||||
projectIdentifier,
|
projectIdentifier,
|
||||||
project: initialProject,
|
project: initialProject,
|
||||||
tool,
|
tool,
|
||||||
|
apiKey,
|
||||||
initialCodeBlock
|
initialCodeBlock
|
||||||
}: {
|
}: {
|
||||||
projectIdentifier: string
|
projectIdentifier: string
|
||||||
project?: Project
|
project?: Project
|
||||||
tool?: string
|
tool?: string
|
||||||
|
apiKey?: string
|
||||||
initialCodeBlock?: JSX.Element
|
initialCodeBlock?: JSX.Element
|
||||||
}) {
|
}) {
|
||||||
const ctx = useAgentic()
|
const ctx = useAgentic()
|
||||||
|
@ -105,6 +107,7 @@ export function ExampleUsage({
|
||||||
<ExampleUsageContent
|
<ExampleUsageContent
|
||||||
projectIdentifier={projectIdentifier}
|
projectIdentifier={projectIdentifier}
|
||||||
tool={tool}
|
tool={tool}
|
||||||
|
apiKey={apiKey}
|
||||||
initialCodeBlock={initialCodeBlock}
|
initialCodeBlock={initialCodeBlock}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isError={isError}
|
isError={isError}
|
||||||
|
@ -119,6 +122,7 @@ export function ExampleUsage({
|
||||||
function ExampleUsageContent({
|
function ExampleUsageContent({
|
||||||
projectIdentifier,
|
projectIdentifier,
|
||||||
tool,
|
tool,
|
||||||
|
apiKey,
|
||||||
initialCodeBlock,
|
initialCodeBlock,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
|
@ -128,6 +132,7 @@ function ExampleUsageContent({
|
||||||
}: {
|
}: {
|
||||||
projectIdentifier: string
|
projectIdentifier: string
|
||||||
tool?: string
|
tool?: string
|
||||||
|
apiKey?: string
|
||||||
initialCodeBlock?: JSX.Element
|
initialCodeBlock?: JSX.Element
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
isError: boolean
|
isError: boolean
|
||||||
|
@ -156,7 +161,8 @@ function ExampleUsageContent({
|
||||||
project,
|
project,
|
||||||
deployment,
|
deployment,
|
||||||
identifier: projectIdentifier,
|
identifier: projectIdentifier,
|
||||||
tool
|
tool,
|
||||||
|
apiKey
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -10,12 +10,12 @@ export function PublicProject({ project }: { project: Project }) {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={project.id}
|
key={project.id}
|
||||||
className='p-2 border rounded-lg hover:border-gray-400
|
className='p-3 border rounded-lg hover:border-gray-400
|
||||||
divide-y divide-gray-200 overflow-hidden bg-white shadow-sm max-w-md flex flex-col gap-2
|
divide-y divide-gray-200 overflow-hidden bg-white shadow-sm max-w-md flex flex-col gap-3
|
||||||
'
|
'
|
||||||
href={`/marketplace/projects/${project.identifier}`}
|
href={`/marketplace/projects/${project.identifier}`}
|
||||||
>
|
>
|
||||||
<div className='p-2 flex gap-2.5 items-center'>
|
<div className='pb-3 flex gap-2.5 items-center'>
|
||||||
<img
|
<img
|
||||||
src={
|
src={
|
||||||
deployment.iconUrl ||
|
deployment.iconUrl ||
|
||||||
|
@ -35,13 +35,15 @@ export function PublicProject({ project }: { project: Project }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex-1 flex flex-col gap-2 justify-between'>
|
<div className='flex-1 flex flex-col gap-3 justify-between'>
|
||||||
{deployment.description && (
|
{deployment.description && (
|
||||||
<p className='text-sm text-gray-700'>{deployment.description}</p>
|
<p className='text-sm text-gray-700 line-clamp-4'>
|
||||||
|
{deployment.description}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{project.lastPublishedDeployment && (
|
{project.lastPublishedDeployment && (
|
||||||
<div className='text-xs text-gray-500 flex items-center justify-between'>
|
<div className='text-xs text-gray-500 flex gap-3 items-center justify-between'>
|
||||||
<div>{deployment.version}</div>
|
<div>{deployment.version}</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -97,6 +97,7 @@ export type GetCodeForDeveloperConfigOpts = {
|
||||||
deployment: Deployment
|
deployment: Deployment
|
||||||
identifier: string
|
identifier: string
|
||||||
tool?: string
|
tool?: string
|
||||||
|
apiKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetCodeForDeveloperConfigInnerOpts = Simplify<
|
type GetCodeForDeveloperConfigInnerOpts = Simplify<
|
||||||
|
@ -169,7 +170,8 @@ export function getCodeForTSFrameworkConfig({
|
||||||
config,
|
config,
|
||||||
identifier,
|
identifier,
|
||||||
prompt,
|
prompt,
|
||||||
systemPrompt
|
systemPrompt,
|
||||||
|
apiKey
|
||||||
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
|
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
|
||||||
switch (config.tsFrameworkTarget) {
|
switch (config.tsFrameworkTarget) {
|
||||||
case 'ai':
|
case 'ai':
|
||||||
|
@ -180,7 +182,13 @@ import { AgenticToolClient } from '@agentic/platform-tool-client'
|
||||||
import { openai } from '@ai-sdk/openai'
|
import { openai } from '@ai-sdk/openai'
|
||||||
import { generateText } from 'ai'
|
import { generateText } from 'ai'
|
||||||
|
|
||||||
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
|
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}'${
|
||||||
|
apiKey
|
||||||
|
? `, {
|
||||||
|
apiKey: '${apiKey}'
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
})
|
||||||
|
|
||||||
const result = await generateText({
|
const result = await generateText({
|
||||||
model: openai('gpt-4o-mini'),
|
model: openai('gpt-4o-mini'),
|
||||||
|
@ -201,7 +209,13 @@ import { AgenticToolClient } from '@agentic/platform-tool-client'
|
||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
|
|
||||||
const openai = new OpenAI()
|
const openai = new OpenAI()
|
||||||
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
|
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}'${
|
||||||
|
apiKey
|
||||||
|
? `, {
|
||||||
|
apiKey: '${apiKey}'
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
})
|
||||||
|
|
||||||
// This example uses OpenAI's Chat Completions API
|
// This example uses OpenAI's Chat Completions API
|
||||||
const res = await openai.chat.completions.create({
|
const res = await openai.chat.completions.create({
|
||||||
|
@ -232,7 +246,13 @@ import { AgenticToolClient } from '@agentic/platform-tool-client'
|
||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
|
|
||||||
const openai = new OpenAI()
|
const openai = new OpenAI()
|
||||||
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
|
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}'${
|
||||||
|
apiKey
|
||||||
|
? `, {
|
||||||
|
apiKey: '${apiKey}'
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
})
|
||||||
|
|
||||||
// This example uses OpenAI's newer Responses API
|
// This example uses OpenAI's newer Responses API
|
||||||
const res = await openai.responses.create({
|
const res = await openai.responses.create({
|
||||||
|
@ -265,7 +285,13 @@ import { ChatPromptTemplate } from '@langchain/core/prompts'
|
||||||
import { ChatOpenAI } from '@langchain/openai'
|
import { ChatOpenAI } from '@langchain/openai'
|
||||||
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents'
|
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents'
|
||||||
|
|
||||||
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
|
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}'${
|
||||||
|
apiKey
|
||||||
|
? `, {
|
||||||
|
apiKey: '${apiKey}'
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
})
|
||||||
|
|
||||||
const agent = createToolCallingAgent({
|
const agent = createToolCallingAgent({
|
||||||
llm: new ChatOpenAI({ model: 'gpt-4o-mini' }),
|
llm: new ChatOpenAI({ model: 'gpt-4o-mini' }),
|
||||||
|
@ -296,7 +322,13 @@ import { AgenticToolClient } from '@agentic/platform-tool-client'
|
||||||
import { openai } from '@llamaindex/openai'
|
import { openai } from '@llamaindex/openai'
|
||||||
import { agent } from '@llamaindex/workflow'
|
import { agent } from '@llamaindex/workflow'
|
||||||
|
|
||||||
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
|
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}'${
|
||||||
|
apiKey
|
||||||
|
? `, {
|
||||||
|
apiKey: '${apiKey}'
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
})
|
||||||
|
|
||||||
const exampleAgent = agent({
|
const exampleAgent = agent({
|
||||||
llm: openai({ model: 'gpt-4o-mini', temperature: 0 }),
|
llm: openai({ model: 'gpt-4o-mini', temperature: 0 }),
|
||||||
|
@ -320,7 +352,13 @@ import { AgenticToolClient } from '@agentic/platform-tool-client'
|
||||||
import { openai } from '@ai-sdk/openai'
|
import { openai } from '@ai-sdk/openai'
|
||||||
import { Agent } from '@mastra/core/agent'
|
import { Agent } from '@mastra/core/agent'
|
||||||
|
|
||||||
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
|
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}'${
|
||||||
|
apiKey
|
||||||
|
? `, {
|
||||||
|
apiKey: '${apiKey}'
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
})
|
||||||
|
|
||||||
const exampleAgent = new Agent({
|
const exampleAgent = new Agent({
|
||||||
name: 'Example Agent',
|
name: 'Example Agent',
|
||||||
|
@ -345,7 +383,13 @@ import { AgenticToolClient } from '@agentic/platform-tool-client'
|
||||||
import { genkit } from 'genkit'
|
import { genkit } from 'genkit'
|
||||||
import { gpt4oMini, openAI } from 'genkitx-openai'
|
import { gpt4oMini, openAI } from 'genkitx-openai'
|
||||||
|
|
||||||
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
|
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}'${
|
||||||
|
apiKey
|
||||||
|
? `, {
|
||||||
|
apiKey: '${apiKey}'
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
})
|
||||||
|
|
||||||
const ai = genkit({
|
const ai = genkit({
|
||||||
plugins: [openAI()]
|
plugins: [openAI()]
|
||||||
|
@ -360,42 +404,18 @@ const result = await ai.generate({
|
||||||
console.log(result)`.trim(),
|
console.log(result)`.trim(),
|
||||||
lang: 'ts'
|
lang: 'ts'
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 'xsai':
|
|
||||||
// return {
|
|
||||||
// code: `
|
|
||||||
// import { AgenticToolClient } from '@agentic/platform-tool-client'
|
|
||||||
// import { createXSAITools } from '@agentic/xsai'
|
|
||||||
// import { generateText } from 'xsai'
|
|
||||||
|
|
||||||
// const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
|
|
||||||
|
|
||||||
// const result = await generateText({
|
|
||||||
// apiKey: process.env.OPENAI_API_KEY!,
|
|
||||||
// baseURL: 'https://api.openai.com/v1/',
|
|
||||||
// model: 'gpt-4o-mini',
|
|
||||||
// tools: await createXSAITools(searchTool),
|
|
||||||
// toolChoice: 'required',
|
|
||||||
// messages: [
|
|
||||||
// {
|
|
||||||
// role: 'user',
|
|
||||||
// content: '${prompt}'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// })
|
|
||||||
|
|
||||||
// console.log(JSON.stringify(result, null, 2))`.trim(),
|
|
||||||
// lang: 'ts'
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCodeForPythonFrameworkConfig({
|
export function getCodeForPythonFrameworkConfig({
|
||||||
config,
|
config,
|
||||||
identifier,
|
identifier,
|
||||||
prompt
|
prompt,
|
||||||
|
apiKey
|
||||||
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
|
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
|
||||||
const mcpUrl = `${gatewayBaseUrl}/${identifier}/mcp`
|
const mcpUrl = `${gatewayBaseUrl}/${identifier}/mcp${
|
||||||
|
apiKey ? `?apiKey=${apiKey}` : ''
|
||||||
|
}`
|
||||||
|
|
||||||
switch (config.pyFrameworkTarget) {
|
switch (config.pyFrameworkTarget) {
|
||||||
case 'openai':
|
case 'openai':
|
||||||
|
@ -479,7 +499,8 @@ export function getCodeForHTTPConfig({
|
||||||
identifier,
|
identifier,
|
||||||
deployment,
|
deployment,
|
||||||
tool,
|
tool,
|
||||||
args
|
args,
|
||||||
|
apiKey
|
||||||
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
|
}: GetCodeForDeveloperConfigInnerOpts): CodeSnippet {
|
||||||
tool ??= deployment.tools[0]?.name
|
tool ??= deployment.tools[0]?.name
|
||||||
assert(tool, 'tool is required')
|
assert(tool, 'tool is required')
|
||||||
|
@ -493,7 +514,9 @@ export function getCodeForHTTPConfig({
|
||||||
|
|
||||||
// TODO: better formatting for the curl command
|
// TODO: better formatting for the curl command
|
||||||
return {
|
return {
|
||||||
code: `curl -X POST -H "Content-Type: application/json" -d '${formattedArgs}' ${url}`,
|
code: `curl -X POST -H "Content-Type: application/json"${
|
||||||
|
apiKey ? ` -H "Authorization: Bearer ${apiKey}"` : ''
|
||||||
|
} -d '${formattedArgs}' ${url}`,
|
||||||
lang: 'bash'
|
lang: 'bash'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,7 +527,9 @@ export function getCodeForHTTPConfig({
|
||||||
.join(' ')
|
.join(' ')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: `http ${url} ${formattedArgs}`,
|
code: `http ${url}${apiKey ? ` Authorization:"Bearer ${apiKey}"` : ''}${
|
||||||
|
formattedArgs ? ` ${formattedArgs}` : ''
|
||||||
|
}`,
|
||||||
lang: 'bash'
|
lang: 'bash'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,10 +122,9 @@ export class ArXivClient extends AIFunctionsProvider {
|
||||||
apiBaseUrl = arxiv.API_BASE_URL,
|
apiBaseUrl = arxiv.API_BASE_URL,
|
||||||
ky = defaultKy
|
ky = defaultKy
|
||||||
}: {
|
}: {
|
||||||
apiKey?: string
|
|
||||||
apiBaseUrl?: string
|
apiBaseUrl?: string
|
||||||
ky?: KyInstance
|
ky?: KyInstance
|
||||||
}) {
|
} = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.apiBaseUrl = apiBaseUrl
|
this.apiBaseUrl = apiBaseUrl
|
||||||
|
|
|
@ -55,7 +55,8 @@ export class OpenMeteoClient extends AIFunctionsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the 7-day weather variables in hourly and daily resolution for given WGS84 latitude and longitude coordinates. Available worldwide.
|
* Gets the 7-day weather variables in hourly and daily resolution for given
|
||||||
|
* WGS84 latitude and longitude coordinates. Available worldwide.
|
||||||
*/
|
*/
|
||||||
@aiFunction({
|
@aiFunction({
|
||||||
name: 'open_meteo_get_forecast',
|
name: 'open_meteo_get_forecast',
|
||||||
|
|
|
@ -347,7 +347,7 @@ export class RedditClient extends AIFunctionsProvider {
|
||||||
* @see https://old.reddit.com/dev/api/#GET_hot
|
* @see https://old.reddit.com/dev/api/#GET_hot
|
||||||
*/
|
*/
|
||||||
@aiFunction({
|
@aiFunction({
|
||||||
name: 'reddit_get_subreddit_posts',
|
name: 'get_subreddit_posts',
|
||||||
description: 'Fetches posts from a subreddit.',
|
description: 'Fetches posts from a subreddit.',
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
subreddit: z.string().describe('The subreddit to fetch posts from.'),
|
subreddit: z.string().describe('The subreddit to fetch posts from.'),
|
||||||
|
|
|
@ -146,7 +146,7 @@ export class TavilyClient extends AIFunctionsProvider {
|
||||||
* Searches the web for pages relevant to the given query and summarizes the results.
|
* Searches the web for pages relevant to the given query and summarizes the results.
|
||||||
*/
|
*/
|
||||||
@aiFunction({
|
@aiFunction({
|
||||||
name: 'tavily_web_search',
|
name: 'search',
|
||||||
description:
|
description:
|
||||||
'Searches the web to find the most relevant pages for a given query and summarizes the results. Very useful for finding up-to-date news and information about any topic.',
|
'Searches the web to find the most relevant pages for a given query and summarizes the results. Very useful for finding up-to-date news and information about any topic.',
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
|
|
|
@ -695,13 +695,13 @@ export class AgenticApiClient {
|
||||||
.json()
|
.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Refreshes a consumer's API token. */
|
/** Refreshes a consumer's API key. */
|
||||||
async refreshConsumerToken({
|
async refreshConsumerApiKey({
|
||||||
consumerId,
|
consumerId,
|
||||||
...searchParams
|
...searchParams
|
||||||
}: OperationParameters<'refreshConsumerToken'>): Promise<Consumer> {
|
}: OperationParameters<'refreshConsumerApiKey'>): Promise<Consumer> {
|
||||||
return this.ky
|
return this.ky
|
||||||
.post(`v1/consumers/${consumerId}/refresh-token`, { searchParams })
|
.post(`v1/consumers/${consumerId}/refresh-api-key`, { searchParams })
|
||||||
.json()
|
.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -849,22 +849,22 @@ export class AgenticApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a consumer by API token. This method is admin-only.
|
* Gets a consumer by API key. This method is admin-only.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async adminGetConsumerByToken<
|
async adminGetConsumerByApiKey<
|
||||||
TPopulate extends NonNullable<
|
TPopulate extends NonNullable<
|
||||||
OperationParameters<'adminGetConsumerByToken'>['populate']
|
OperationParameters<'adminGetConsumerByApiKey'>['populate']
|
||||||
>[number]
|
>[number]
|
||||||
>({
|
>({
|
||||||
token,
|
apiKey,
|
||||||
...searchParams
|
...searchParams
|
||||||
}: OperationParameters<'adminGetConsumerByToken'> & {
|
}: OperationParameters<'adminGetConsumerByApiKey'> & {
|
||||||
populate?: TPopulate[]
|
populate?: TPopulate[]
|
||||||
}): Promise<PopulateConsumer<TPopulate, AdminConsumer>> {
|
}): Promise<PopulateConsumer<TPopulate, AdminConsumer>> {
|
||||||
return this.ky
|
return this.ky
|
||||||
.get(`v1/admin/consumers/tokens/${token}`, {
|
.get(`v1/admin/consumers/api-keys/${apiKey}`, {
|
||||||
searchParams: sanitizeSearchParams(searchParams)
|
searchParams: sanitizeSearchParams(searchParams)
|
||||||
})
|
})
|
||||||
.json()
|
.json()
|
||||||
|
@ -872,7 +872,7 @@ export class AgenticApiClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activates a consumer signifying that at least one API call has been made
|
* Activates a consumer signifying that at least one API call has been made
|
||||||
* using the consumer's API token. This method is idempotent and admin-only.
|
* using the consumer's API key. This method is idempotent and admin-only.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -421,7 +421,7 @@ export interface paths {
|
||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/v1/consumers/{consumerId}/refresh-token": {
|
"/v1/consumers/{consumerId}/refresh-api-key": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
|
@ -430,8 +430,8 @@ export interface paths {
|
||||||
};
|
};
|
||||||
get?: never;
|
get?: never;
|
||||||
put?: never;
|
put?: never;
|
||||||
/** @description Refreshes a consumer's API token. */
|
/** @description Refreshes a consumer's API key. */
|
||||||
post: operations["refreshConsumerToken"];
|
post: operations["refreshConsumerApiKey"];
|
||||||
delete?: never;
|
delete?: never;
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
|
@ -525,15 +525,15 @@ export interface paths {
|
||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/v1/admin/consumers/tokens/{token}": {
|
"/v1/admin/consumers/api-keys/{apiKey}": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
/** @description Gets a consumer by API token. This route is admin-only. */
|
/** @description Gets a consumer by API key. This route is admin-only. */
|
||||||
get: operations["adminGetConsumerByToken"];
|
get: operations["adminGetConsumerByApiKey"];
|
||||||
put?: never;
|
put?: never;
|
||||||
post?: never;
|
post?: never;
|
||||||
delete?: never;
|
delete?: never;
|
||||||
|
@ -2275,7 +2275,7 @@ export interface operations {
|
||||||
410: components["responses"]["410"];
|
410: components["responses"]["410"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
refreshConsumerToken: {
|
refreshConsumerApiKey: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
|
@ -2584,15 +2584,15 @@ export interface operations {
|
||||||
404: components["responses"]["404"];
|
404: components["responses"]["404"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
adminGetConsumerByToken: {
|
adminGetConsumerByApiKey: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
populate?: ("user" | "project" | "deployment") | ("user" | "project" | "deployment")[];
|
populate?: ("user" | "project" | "deployment") | ("user" | "project" | "deployment")[];
|
||||||
};
|
};
|
||||||
header?: never;
|
header?: never;
|
||||||
path: {
|
path: {
|
||||||
/** @description Consumer token */
|
/** @description Consumer API key */
|
||||||
token: string;
|
apiKey: string;
|
||||||
};
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
|
|
5
todo.md
5
todo.md
|
@ -38,7 +38,8 @@
|
||||||
- add disclaimer about public beta
|
- add disclaimer about public beta
|
||||||
- add search / sorting
|
- add search / sorting
|
||||||
- add admin-based tags for main page layout (featured, etc)
|
- add admin-based tags for main page layout (featured, etc)
|
||||||
- replace render for api and/or add turbo for caching
|
- replace render for api and/or add turbo for caching (too slow to deploy)
|
||||||
|
- public-project limit `description` to max 5 lines and show ellipsis
|
||||||
|
|
||||||
## TODO: Post-MVP
|
## TODO: Post-MVP
|
||||||
|
|
||||||
|
@ -135,3 +136,5 @@
|
||||||
- basic account page on website
|
- basic account page on website
|
||||||
- edit name, profile photo, etc
|
- edit name, profile photo, etc
|
||||||
- **public project detail page metadata**
|
- **public project detail page metadata**
|
||||||
|
- fix readme css <img height="..."> not taking effect because of tailwind css preflight which sets `img, video { height: auto }`
|
||||||
|
- we still want this for every other scenario; just want to sandbox the github-style readme markdown css...
|
||||||
|
|
Ładowanie…
Reference in New Issue