Merge pull request #719 from transitive-bullshit/feature/rework-api-keys

pull/721/head
Travis Fischer 2025-07-02 15:41:21 -05:00 zatwierdzone przez GitHub
commit dd49abc20a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
20 zmienionych plików z 168 dodań i 114 usunięć

Wyświetl plik

@ -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))

Wyświetl plik

@ -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()

Wyświetl plik

@ -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'
} }
}) })

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()

Wyświetl plik

@ -0,0 +1,5 @@
import { sha256 } from '@agentic/platform-core'
export async function createConsumerApiKey(): Promise<string> {
return `sk-${sha256()}`
}

Wyświetl plik

@ -1,5 +0,0 @@
import { sha256 } from '@agentic/platform-core'
export async function createConsumerToken(): Promise<string> {
return sha256()
}

Wyświetl plik

@ -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
} }

Wyświetl plik

@ -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 (

Wyświetl plik

@ -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>

Wyświetl plik

@ -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'
} }
} }

Wyświetl plik

@ -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

Wyświetl plik

@ -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',

Wyświetl plik

@ -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.'),

Wyświetl plik

@ -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({

Wyświetl plik

@ -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
*/ */

Wyświetl plik

@ -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;
}; };

Wyświetl plik

@ -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...