kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: fix all the file storage logic
rodzic
762953e8c3
commit
a513194a2f
|
@ -35,8 +35,8 @@
|
|||
"@agentic/platform-hono": "workspace:*",
|
||||
"@agentic/platform-types": "workspace:*",
|
||||
"@agentic/platform-validators": "workspace:*",
|
||||
"@aws-sdk/client-s3": "^3.840.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.840.0",
|
||||
"@aws-sdk/client-s3": "^3.726.1",
|
||||
"@aws-sdk/s3-request-presigner": "^3.726.1",
|
||||
"@dicebear/collection": "catalog:",
|
||||
"@dicebear/core": "catalog:",
|
||||
"@fisch0920/drizzle-orm": "catalog:",
|
||||
|
@ -50,6 +50,7 @@
|
|||
"file-type": "^21.0.0",
|
||||
"hono": "catalog:",
|
||||
"ky": "catalog:",
|
||||
"mrmime": "^2.0.1",
|
||||
"octokit": "catalog:",
|
||||
"p-all": "catalog:",
|
||||
"postgres": "catalog:",
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
openapiErrorResponse409,
|
||||
openapiErrorResponses
|
||||
} from '@/lib/openapi-utils'
|
||||
import { uploadFileToStorage } from '@/lib/storage'
|
||||
import { uploadFileUrlToStorage } from '@/lib/storage'
|
||||
|
||||
import { createDeploymentQuerySchema } from './schemas'
|
||||
|
||||
|
@ -154,9 +154,9 @@ export function registerV1CreateDeployment(
|
|||
const agenticProjectConfig = await resolveAgenticProjectConfig(body, {
|
||||
label: `deployment "${deploymentIdentifier}"`,
|
||||
logger,
|
||||
uploadFileToStorage: async (source) => {
|
||||
return uploadFileToStorage(source, {
|
||||
projectIdentifier
|
||||
uploadFileUrlToStorage: async (source) => {
|
||||
return uploadFileUrlToStorage(source, {
|
||||
prefix: projectIdentifier
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Storage > uploadFileUrlToStorage data-uri 1`] = `"https://storage.agentic.so/@dev/test/ef4238fba78887e0974cd48809a66e284cdd78ce92d6b2d485c25a552fb39631.svg"`;
|
||||
|
||||
exports[`Storage > uploadFileUrlToStorage data-uri 2 1`] = `"https://storage.agentic.so/@dev/test/efbc1f0409b730d93b01c918be9e024bf7777801cfd252221c39e36c08c1b4fb"`;
|
||||
|
||||
exports[`Storage > uploadFileUrlToStorage url 1`] = `"https://storage.agentic.so/@dev/test/6da6ef895be2a42606b99e5e3b9c25687c92c81986a9718e07f32e574d41cf7a.svg"`;
|
|
@ -5,6 +5,7 @@ import { and, db, eq, type RawAccount, type RawUser, schema } from '@/db'
|
|||
|
||||
import { createAvatar } from '../create-avatar'
|
||||
import { getUniqueNamespace } from '../ensure-unique-namespace'
|
||||
import { uploadFileUrlToStorage } from '../storage'
|
||||
|
||||
/**
|
||||
* After a user completes an authentication flow, we'll have partial account info
|
||||
|
@ -51,9 +52,6 @@ export async function upsertOrLinkUserAccount({
|
|||
}): Promise<RawUser> {
|
||||
const { provider, accountId } = partialAccount
|
||||
|
||||
// Set a default profile image if one isn't provided
|
||||
partialUser.image ??= createAvatar(partialUser.email)
|
||||
|
||||
const [existingAccount, existingUser] = await Promise.all([
|
||||
db.query.accounts.findFirst({
|
||||
where: and(
|
||||
|
@ -70,6 +68,16 @@ export async function upsertOrLinkUserAccount({
|
|||
})
|
||||
])
|
||||
|
||||
async function resolveUserProfileImage({ prefix }: { prefix: string }) {
|
||||
// Set a default profile image if one isn't provided
|
||||
partialUser.image = await uploadFileUrlToStorage(
|
||||
partialUser.image ?? createAvatar(partialUser.email),
|
||||
{
|
||||
prefix
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (existingAccount && existingUser) {
|
||||
// Happy path case: the user is just logging in with an existing account
|
||||
// that's already linked to a user.
|
||||
|
@ -98,10 +106,19 @@ export async function upsertOrLinkUserAccount({
|
|||
// the one in the account we're linking, we should throw an error unless it's
|
||||
// a "trusted" provider.
|
||||
if (provider === 'password' && existingUser.email !== partialUser.email) {
|
||||
await db
|
||||
await resolveUserProfileImage({ prefix: existingUser.username })
|
||||
|
||||
const [user] = await db
|
||||
.update(schema.users)
|
||||
.set(partialUser)
|
||||
.where(eq(schema.users.id, existingUser.id))
|
||||
.returning()
|
||||
assert(
|
||||
user,
|
||||
500,
|
||||
`Error updating existing user during ${provider} authentication`
|
||||
)
|
||||
return user
|
||||
}
|
||||
|
||||
return existingUser
|
||||
|
@ -124,6 +141,8 @@ export async function upsertOrLinkUserAccount({
|
|||
{ label: 'Username' }
|
||||
)
|
||||
|
||||
await resolveUserProfileImage({ prefix: username })
|
||||
|
||||
// This is a user's first time signing up with the platform, so create both
|
||||
// a new user and linked account.
|
||||
return db.transaction(async (tx) => {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import {
|
||||
deleteStorageObject,
|
||||
getStorageObject,
|
||||
putStorageObject
|
||||
putStorageObject,
|
||||
uploadFileUrlToStorage
|
||||
} from './storage'
|
||||
|
||||
describe('Storage', () => {
|
||||
it('putObject, getObject, deleteObject', async () => {
|
||||
test('putObject, getObject, deleteObject', async () => {
|
||||
if (!process.env.ACCESS_KEY_ID) {
|
||||
// TODO: ignore on CI
|
||||
expect(true).toEqual(true)
|
||||
|
@ -27,4 +28,43 @@ describe('Storage', () => {
|
|||
const res = await deleteStorageObject('test.txt')
|
||||
expect(res.$metadata.httpStatusCode).toEqual(204)
|
||||
})
|
||||
|
||||
test('uploadFileUrlToStorage url', async () => {
|
||||
const url = await uploadFileUrlToStorage(
|
||||
'https://agentic.so/agentic-icon-circle-light.svg',
|
||||
{
|
||||
prefix: '@dev/test'
|
||||
}
|
||||
)
|
||||
|
||||
expect(url).toBeTruthy()
|
||||
expect(new URL(url).origin).toEqual('https://storage.agentic.so')
|
||||
expect(url).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('uploadFileUrlToStorage data-uri', async () => {
|
||||
const url = await uploadFileUrlToStorage(
|
||||
'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22400%22%20height%3D%22400%22%20viewBox%3D%220%200%20124%20124%22%20fill%3D%22none%22%3E%3Crect%20width%3D%22124%22%20height%3D%22124%22%20rx%3D%2224%22%20fill%3D%22%23F97316%22%2F%3E%3Cpath%20d%3D%22M19.375%2036.7818V100.625C19.375%20102.834%2021.1659%20104.625%2023.375%20104.625H87.2181C90.7818%20104.625%2092.5664%20100.316%2090.0466%2097.7966L26.2034%2033.9534C23.6836%2031.4336%2019.375%2033.2182%2019.375%2036.7818Z%22%20fill%3D%22white%22%2F%3E%3Ccircle%20cx%3D%2263.2109%22%20cy%3D%2237.5391%22%20r%3D%2218.1641%22%20fill%3D%22black%22%2F%3E%3Crect%20opacity%3D%220.4%22%20x%3D%2281.1328%22%20y%3D%2280.7198%22%20width%3D%2217.5687%22%20height%3D%2217.3876%22%20rx%3D%224%22%20transform%3D%22rotate(-45%2081.1328%2080.7198)%22%20fill%3D%22%23FDBA74%22%2F%3E%3C%2Fsvg%3E',
|
||||
{
|
||||
prefix: '@dev/test'
|
||||
}
|
||||
)
|
||||
|
||||
expect(url).toBeTruthy()
|
||||
expect(new URL(url).origin).toEqual('https://storage.agentic.so')
|
||||
expect(url).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('uploadFileUrlToStorage data-uri 2', async () => {
|
||||
const url = await uploadFileUrlToStorage(
|
||||
'data:application/octet-stream;base64,IyBUZXN0IEV2ZXJ5dGhpbmcgT3BlbkFQSQoKVGhpcyBpcyB0ZXN0aW5nICoqcmVhZG1lIHJlbmRlcmluZyoqLgoKIyMgTWlzYwoKLSBbIF0gSXRlbSAxCi0gWyBdIEl0ZW0gMgotIFt4XSBJdGVtIDMKCi0tLQoKLSBfaXRhbGljXwotICoqYm9sZCoqCi0gW2xpbmtdKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20pCi0gYGNvZGVgCgojIyBDb2RlCgpgYGB0cwpjb25zdCBhID0gMQoKZXhwb3J0IGZ1bmN0aW9uIGZvbygpIHsKICBjb25zb2xlLmxvZygnaGVsbG8gd29ybGQnKQp9CmBgYAoKIyMgSW1hZ2VzCgohW0ltYWdlXShodHRwczovL3BsYWNlaG9sZC5jby82MDB4NDAwKQo=',
|
||||
{
|
||||
prefix: '@dev/test'
|
||||
}
|
||||
)
|
||||
|
||||
expect(url).toBeTruthy()
|
||||
expect(new URL(url).origin).toEqual('https://storage.agentic.so')
|
||||
expect(url).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
||||
import { fileTypeFromBuffer } from 'file-type'
|
||||
import ky from 'ky'
|
||||
import { lookup as lookupMimeType } from 'mrmime'
|
||||
|
||||
import { env } from './env'
|
||||
|
||||
|
@ -25,7 +26,9 @@ export const storageClient = new S3Client({
|
|||
credentials: {
|
||||
accessKeyId: env.S3_ACCESS_KEY_ID,
|
||||
secretAccessKey: env.S3_ACCESS_KEY_SECRET
|
||||
}
|
||||
},
|
||||
requestChecksumCalculation: 'WHEN_REQUIRED',
|
||||
responseChecksumValidation: 'WHEN_REQUIRED'
|
||||
})
|
||||
|
||||
// This ensures that buckets are created automatically if they don't exist on
|
||||
|
@ -71,13 +74,17 @@ export async function deleteStorageObject(
|
|||
}
|
||||
|
||||
export function getStorageObjectInternalUrl(key: string) {
|
||||
return `${env.AGENTIC_STORAGE_BASE_URL}/${Bucket}/${key}`
|
||||
return `${env.S3_ENDPOINT}/${Bucket}/${key}`
|
||||
}
|
||||
|
||||
export function getStorageObjectPublicUrl(key: string) {
|
||||
return `${env.AGENTIC_STORAGE_BASE_URL}/${key}`
|
||||
}
|
||||
|
||||
// TODO: Signed uploads don't seem to be working; getting a signature mismatch
|
||||
// error, and no idea why. Switched to using non-presigned URL uploads for now,
|
||||
// but this will be necessary in the future, for instance, for the web client
|
||||
// to upload files.
|
||||
export async function getStorageSignedUploadUrl(
|
||||
key: string,
|
||||
{
|
||||
|
@ -93,21 +100,21 @@ export async function getStorageSignedUploadUrl(
|
|||
)
|
||||
}
|
||||
|
||||
export async function uploadFileToStorage(
|
||||
input: string,
|
||||
export async function uploadFileUrlToStorage(
|
||||
inputUrl: string,
|
||||
{
|
||||
projectIdentifier
|
||||
prefix
|
||||
}: {
|
||||
projectIdentifier: string
|
||||
prefix: string
|
||||
}
|
||||
): Promise<string> {
|
||||
let source: URL
|
||||
|
||||
try {
|
||||
source = new URL(input)
|
||||
source = new URL(inputUrl)
|
||||
} catch {
|
||||
// Not a URL
|
||||
throw new Error(`Invalid source file URL: ${input}`)
|
||||
throw new Error(`Invalid source file URL: ${inputUrl}`)
|
||||
}
|
||||
|
||||
if (source.hostname === 'storage.agentic.so') {
|
||||
|
@ -117,29 +124,71 @@ export async function uploadFileToStorage(
|
|||
|
||||
const sourceBuffer = await ky.get(source).arrayBuffer()
|
||||
|
||||
const [hash, fileType] = await Promise.all([
|
||||
const [hash, inferredFileType] = await Promise.all([
|
||||
sha256(sourceBuffer),
|
||||
fileTypeFromBuffer(sourceBuffer)
|
||||
])
|
||||
|
||||
const filename = fileType ? `${hash}.${fileType.ext}` : hash
|
||||
const key = `${projectIdentifier}/${filename}`
|
||||
const maybeSourceExt = source.pathname.split('.').at(-1)
|
||||
const maybeSourceExt2 = maybeSourceExt
|
||||
? /^[a-z]+$/i.test(maybeSourceExt)
|
||||
? maybeSourceExt
|
||||
: undefined
|
||||
: undefined
|
||||
const maybeSourceMime =
|
||||
source.protocol === 'data:'
|
||||
? source.pathname.split(',')[0]?.split(';')[0]
|
||||
: undefined
|
||||
const sourceExt0 =
|
||||
maybeSourceExt2 ??
|
||||
(maybeSourceMime && maybeSourceMime !== 'application/octet-stream'
|
||||
? maybeSourceMime.split('/')[1]?.split('+')[0]
|
||||
: undefined)
|
||||
const sourceExt = sourceExt0 === 'markdown' ? 'md' : sourceExt0
|
||||
|
||||
const fileType =
|
||||
inferredFileType ??
|
||||
(sourceExt
|
||||
? {
|
||||
ext: sourceExt,
|
||||
mime: lookupMimeType(sourceExt)
|
||||
}
|
||||
: undefined)
|
||||
|
||||
const filename = fileType?.ext ? `${hash}.${fileType.ext}` : hash
|
||||
const key = `${prefix}/${filename}`
|
||||
|
||||
const publicObjectUrl = getStorageObjectPublicUrl(key)
|
||||
|
||||
// console.log('uploading to r2', {
|
||||
// key,
|
||||
// source,
|
||||
// sourceExt,
|
||||
// maybeSourceMime,
|
||||
// maybeSourceExt,
|
||||
// maybeSourceExt2,
|
||||
// inputUrl,
|
||||
// fileType,
|
||||
// publicObjectUrl
|
||||
// })
|
||||
|
||||
try {
|
||||
// Check if the object already exists.
|
||||
await ky.head(publicObjectUrl)
|
||||
} catch {
|
||||
const signedUploadUrl = await getStorageSignedUploadUrl(key)
|
||||
const body = Buffer.from(sourceBuffer)
|
||||
|
||||
// Object doesn't exist yet, so upload it.
|
||||
await ky.post(signedUploadUrl, {
|
||||
body: sourceBuffer,
|
||||
headers: {
|
||||
'Content-Type': fileType?.mime ?? 'application/octet-stream'
|
||||
}
|
||||
})
|
||||
try {
|
||||
await putStorageObject(key, body, {
|
||||
ContentType: fileType?.mime ?? 'application/octet-stream'
|
||||
})
|
||||
} catch (err: any) {
|
||||
const error = await err.response.text()
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('error uploading to r2', err.message, error)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return publicObjectUrl
|
||||
|
|
|
@ -16,8 +16,7 @@ export async function deployProjects(
|
|||
projects,
|
||||
async (project) => {
|
||||
const config = await loadAgenticConfig({
|
||||
cwd: project,
|
||||
agenticApiClient: client
|
||||
cwd: project
|
||||
})
|
||||
const deployment = await client.createDeployment(config)
|
||||
console.log(`Deployed ${project} => ${deployment.identifier}`)
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
import type { Project } from '@agentic/platform-types'
|
||||
import { assert, omit, sanitizeSearchParams } from '@agentic/platform-core'
|
||||
import ky from 'ky'
|
||||
import { ChevronsUpDownIcon, ExternalLinkIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import plur from 'plur'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useAsync } from 'react-use'
|
||||
|
||||
import { useAgentic } from '@/components/agentic-provider'
|
||||
import { CodeBlock } from '@/components/code-block'
|
||||
|
@ -189,6 +191,14 @@ export function MarketplacePublicProjectDetail({
|
|||
return tab as MarketplacePublicProjectDetailTab
|
||||
}, [searchParams, deployment])
|
||||
|
||||
const { value: readme } = useAsync(async () => {
|
||||
if (tab === 'readme' && deployment?.readme?.trim()) {
|
||||
return ky.get(deployment.readme).text()
|
||||
}
|
||||
|
||||
return undefined
|
||||
}, [deployment, tab])
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<section>
|
||||
|
@ -332,12 +342,9 @@ export function MarketplacePublicProjectDetail({
|
|||
</TabsContent>
|
||||
)}
|
||||
|
||||
{deployment?.readme?.trim() && tab === 'readme' && (
|
||||
{tab === 'readme' && readme && (
|
||||
<TabsContent value='readme' className='flex flex-col gap-4'>
|
||||
<SSRMarkdown
|
||||
markdown={deployment.readme}
|
||||
className='items-start!'
|
||||
/>
|
||||
<SSRMarkdown markdown={readme} className='items-start!' />
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
|
|
|
@ -47,13 +47,16 @@ Examples: `1.0.0`, `0.0.1`, `5.0.1`, etc.
|
|||
</ResponseField>
|
||||
|
||||
<ResponseField name='readme' type='string'>
|
||||
Optional embedded markdown readme documenting the project. Supports
|
||||
GitHub-flavored markdown.
|
||||
Optional markdown readme documenting the project (supports GitHub-flavored markdown).
|
||||
|
||||
A string which may be either: a URL to a remote markdown file (eg, `https://example.com/readme.md`), a local file path (eg, `./readme.md`), or a data-uri string (eg, `data:text/markdown;base64,SGVsbG8gV29ybGQ=`).
|
||||
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name='iconUrl' type='string'>
|
||||
Optional logo image URL to use for the project. Logos should have a square
|
||||
aspect ratio.
|
||||
Optional logo image to use for the project. Logos should have a square aspect ratio.
|
||||
|
||||
A string which may be either: a URL to a remote image (eg, `https://example.com/logo.png`), a local file path (eg, `./logo.png`), or a data-uri string (eg, `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...`).
|
||||
|
||||
</ResponseField>
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="48" cy="48" r="48" fill="#888888"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.2922 73.2067C22.6248 73.255 18.9825 73.2065 15.3653 73.0611C26.752 55.048 38.1402 37.0309 49.5299 19.0097C51.1635 17.3293 52.9847 17.1107 54.9933 18.3541C57.491 20.4558 58.2438 23.0539 57.2515 26.1485C55.4913 29.1352 53.6945 32.0975 51.8609 35.0357C44.1878 47.1766 36.5149 59.3176 28.8417 71.4585C28.2861 72.4763 27.4363 73.059 26.2922 73.2067Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.4302 37.3668C55.7566 37.5224 56.0237 37.7651 56.2315 38.0952C64.3606 49.8615 72.5437 61.5897 80.7804 73.2796C77.1867 73.3768 73.5931 73.3768 69.9993 73.2796C68.9793 73.049 68.1295 72.5391 67.4497 71.7499C62.3379 64.2943 57.1902 56.864 52.0065 49.4591C51.4627 48.6715 50.9772 47.8459 50.5496 46.9824C50.3708 45.8664 50.6137 44.8466 51.278 43.9229C52.6986 41.7519 54.0827 39.5666 55.4302 37.3668Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M63.8803 73.061C59.7288 73.2064 55.5523 73.255 51.3509 73.2067C50.2417 71.5381 49.1005 69.887 47.9271 68.2532C46.8451 69.884 45.8009 71.5351 44.7948 73.2067C40.6904 73.255 36.611 73.2064 32.5567 73.061C36.3849 67.0221 40.2457 61.0003 44.1392 54.9953C45.7173 53.3419 47.5871 52.9292 49.7483 53.757C50.3223 54.0317 50.8322 54.396 51.278 54.8497C55.5109 60.9027 59.7117 66.9732 63.8803 73.061Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.3652 73.0611C18.9825 73.2065 22.6247 73.255 26.2921 73.2067C22.625 73.4004 18.9342 73.4004 15.2195 73.2067C15.2376 73.1183 15.2861 73.0698 15.3652 73.0611Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.5568 73.0611C36.6111 73.2065 40.6904 73.255 44.7948 73.2067C40.6907 73.4004 36.5629 73.4004 32.4111 73.2067C32.4292 73.1183 32.4777 73.0698 32.5568 73.0611Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M63.8803 73.0611C63.9594 73.0698 64.0079 73.1183 64.026 73.2067C59.7771 73.4005 55.552 73.4005 51.3509 73.2067C55.5523 73.255 59.7289 73.2065 63.8803 73.0611Z" fill="white"/>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
|
@ -372,13 +372,13 @@ export class AgenticApiClient {
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const publicObjectUrl = await client.uploadFileToStorage(
|
||||
* const publicObjectUrl = await client.uploadFileUrlToStorage(
|
||||
* new URL('https://example.com/image.png'),
|
||||
* { projectIdentifier: '@username/my-project' }
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
async uploadFileToStorage(
|
||||
async uploadFileUrlToStorage(
|
||||
source: string | ArrayBuffer | URL,
|
||||
{
|
||||
projectIdentifier
|
||||
|
|
|
@ -15,6 +15,7 @@ import { registerSignoutCommand } from './commands/signout'
|
|||
import { registerSignupCommand } from './commands/signup'
|
||||
import { registerWhoAmICommand } from './commands/whoami'
|
||||
import { AuthStore } from './lib/auth-store'
|
||||
import { agenticApiBaseUrl } from './lib/env'
|
||||
import { initExitHooks } from './lib/exit-hooks'
|
||||
import { createErrorHandler } from './lib/handle-error'
|
||||
|
||||
|
@ -23,7 +24,7 @@ async function main() {
|
|||
|
||||
// Initialize the API client
|
||||
const client = new AgenticApiClient({
|
||||
apiBaseUrl: process.env.AGENTIC_API_BASE_URL,
|
||||
apiBaseUrl: agenticApiBaseUrl,
|
||||
onUpdateAuth: (update) => {
|
||||
if (update) {
|
||||
AuthStore.setAuth(update)
|
||||
|
|
|
@ -8,14 +8,13 @@ import type { Context } from '../types'
|
|||
export function registerDebugCommand({
|
||||
program,
|
||||
logger,
|
||||
handleError,
|
||||
client
|
||||
handleError
|
||||
}: Context) {
|
||||
const command = new Command('debug')
|
||||
.description('Prints config for a local project.')
|
||||
.option(
|
||||
'-c, --cwd <dir>',
|
||||
'The directory to load the Agentic project config from (defaults to cwd). This directory must contain an "agentic.config.{ts,js,json}" project file.'
|
||||
'The directory to load the Agentic project config from (defaults to the cwd). This directory must contain an "agentic.config.{ts,js,json}" project file.'
|
||||
)
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
|
@ -27,8 +26,7 @@ export function registerDebugCommand({
|
|||
// client.
|
||||
const config = await oraPromise(
|
||||
loadAgenticConfig({
|
||||
cwd: opts.cwd,
|
||||
agenticApiClient: client
|
||||
cwd: opts.cwd
|
||||
}),
|
||||
{
|
||||
text: `Loading Agentic config from ${opts.cwd ?? process.cwd()}`,
|
||||
|
|
|
@ -16,7 +16,7 @@ export function registerDeployCommand({
|
|||
.description('Creates a new deployment.')
|
||||
.option(
|
||||
'-c, --cwd <dir>',
|
||||
'The directory to load the Agentic project config from (defaults to cwd). This directory must contain an "agentic.config.{ts,js,json}" project file.'
|
||||
'The directory to load the Agentic project config from (defaults to the cwd). This directory must contain an "agentic.config.{ts,js,json}" project file.'
|
||||
)
|
||||
.option('-d, --debug', 'Print out the parsed agentic config and return.')
|
||||
// TODO
|
||||
|
@ -33,8 +33,7 @@ export function registerDeployCommand({
|
|||
// client.
|
||||
const config = await oraPromise(
|
||||
loadAgenticConfig({
|
||||
cwd: opts.cwd,
|
||||
agenticApiClient: client
|
||||
cwd: opts.cwd
|
||||
}),
|
||||
{
|
||||
text: `Loading Agentic config from ${opts.cwd ?? process.cwd()}`,
|
||||
|
|
|
@ -2,11 +2,13 @@ import type { AuthSession } from '@agentic/platform-types'
|
|||
import { assert } from '@agentic/platform-core'
|
||||
import Conf from 'conf'
|
||||
|
||||
const keyAuthSession = 'authSession'
|
||||
import { agenticApiBaseUrl } from './env'
|
||||
|
||||
const keyAuthSession = `authSession:${agenticApiBaseUrl}`
|
||||
|
||||
export const AuthStore = {
|
||||
store: new Conf<{
|
||||
authSession: AuthSession
|
||||
[keyAuthSession]: AuthSession
|
||||
}>({ projectName: 'agentic' }),
|
||||
|
||||
isAuthenticated() {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export const agenticApiBaseUrl =
|
||||
process.env.AGENTIC_API_BASE_URL || 'https://api.agentic.so'
|
|
@ -19,8 +19,7 @@ export async function resolveDeployment({
|
|||
}): Promise<Deployment> {
|
||||
if (!deploymentIdentifier) {
|
||||
const config = await loadAgenticConfig({
|
||||
cwd,
|
||||
agenticApiClient: client
|
||||
cwd
|
||||
})
|
||||
|
||||
// TODO: re-add team support
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"@agentic/platform-types": "workspace:*",
|
||||
"@agentic/platform-validators": "workspace:*",
|
||||
"@modelcontextprotocol/sdk": "catalog:",
|
||||
"mrmime": "^2.0.1",
|
||||
"semver": "catalog:",
|
||||
"unconfig": "catalog:"
|
||||
},
|
||||
|
|
|
@ -268,7 +268,7 @@ exports[`loadAgenticConfig > everything-openapi 1`] = `
|
|||
"slug": "pro",
|
||||
},
|
||||
],
|
||||
"readme": "data:application/octet-stream;base64,IyBUZXN0IEV2ZXJ5dGhpbmcgT3BlbkFQSQoKVGhpcyBpcyB0ZXN0aW5nICoqcmVhZG1lIHJlbmRlcmluZyoqLgoKIyMgTWlzYwoKLSBbIF0gSXRlbSAxCi0gWyBdIEl0ZW0gMgotIFt4XSBJdGVtIDMKCi0tLQoKLSBfaXRhbGljXwotICoqYm9sZCoqCi0gW2xpbmtdKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20pCi0gYGNvZGVgCgojIyBDb2RlCgpgYGB0cwpjb25zdCBhID0gMQoKZXhwb3J0IGZ1bmN0aW9uIGZvbygpIHsKICBjb25zb2xlLmxvZygnaGVsbG8gd29ybGQnKQp9CmBgYAoKIyMgSW1hZ2VzCgohW0ltYWdlXShodHRwczovL3BsYWNlaG9sZC5jby82MDB4NDAwKQo=",
|
||||
"readme": "data:text/markdown;base64,IyBUZXN0IEV2ZXJ5dGhpbmcgT3BlbkFQSQoKVGhpcyBpcyB0ZXN0aW5nICoqcmVhZG1lIHJlbmRlcmluZyoqLgoKIyMgTWlzYwoKLSBbIF0gSXRlbSAxCi0gWyBdIEl0ZW0gMgotIFt4XSBJdGVtIDMKCi0tLQoKLSBfaXRhbGljXwotICoqYm9sZCoqCi0gW2xpbmtdKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20pCi0gYGNvZGVgCgojIyBDb2RlCgpgYGB0cwpjb25zdCBhID0gMQoKZXhwb3J0IGZ1bmN0aW9uIGZvbygpIHsKICBjb25zb2xlLmxvZygnaGVsbG8gd29ybGQnKQp9CmBgYAoKIyMgSW1hZ2VzCgohW0ltYWdlXShodHRwczovL3BsYWNlaG9sZC5jby82MDB4NDAwKQo=",
|
||||
"slug": "test-everything-openapi",
|
||||
"toolConfigs": [
|
||||
{
|
||||
|
@ -414,7 +414,7 @@ exports[`loadAgenticConfig > metadata-0 1`] = `
|
|||
"limit": 1000,
|
||||
"mode": "approximate",
|
||||
},
|
||||
"icon": "data:application/octet-stream;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iNDgiIGN5PSI0OCIgcj0iNDgiIGZpbGw9IiM4ODg4ODgiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yNi4yOTIyIDczLjIwNjdDMjIuNjI0OCA3My4yNTUgMTguOTgyNSA3My4yMDY1IDE1LjM2NTMgNzMuMDYxMUMyNi43NTIgNTUuMDQ4IDM4LjE0MDIgMzcuMDMwOSA0OS41Mjk5IDE5LjAwOTdDNTEuMTYzNSAxNy4zMjkzIDUyLjk4NDcgMTcuMTEwNyA1NC45OTMzIDE4LjM1NDFDNTcuNDkxIDIwLjQ1NTggNTguMjQzOCAyMy4wNTM5IDU3LjI1MTUgMjYuMTQ4NUM1NS40OTEzIDI5LjEzNTIgNTMuNjk0NSAzMi4wOTc1IDUxLjg2MDkgMzUuMDM1N0M0NC4xODc4IDQ3LjE3NjYgMzYuNTE0OSA1OS4zMTc2IDI4Ljg0MTcgNzEuNDU4NUMyOC4yODYxIDcyLjQ3NjMgMjcuNDM2MyA3My4wNTkgMjYuMjkyMiA3My4yMDY3WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik01NS40MzAyIDM3LjM2NjhDNTUuNzU2NiAzNy41MjI0IDU2LjAyMzcgMzcuNzY1MSA1Ni4yMzE1IDM4LjA5NTJDNjQuMzYwNiA0OS44NjE1IDcyLjU0MzcgNjEuNTg5NyA4MC43ODA0IDczLjI3OTZDNzcuMTg2NyA3My4zNzY4IDczLjU5MzEgNzMuMzc2OCA2OS45OTkzIDczLjI3OTZDNjguOTc5MyA3My4wNDkgNjguMTI5NSA3Mi41MzkxIDY3LjQ0OTcgNzEuNzQ5OUM2Mi4zMzc5IDY0LjI5NDMgNTcuMTkwMiA1Ni44NjQgNTIuMDA2NSA0OS40NTkxQzUxLjQ2MjcgNDguNjcxNSA1MC45NzcyIDQ3Ljg0NTkgNTAuNTQ5NiA0Ni45ODI0QzUwLjM3MDggNDUuODY2NCA1MC42MTM3IDQ0Ljg0NjYgNTEuMjc4IDQzLjkyMjlDNTIuNjk4NiA0MS43NTE5IDU0LjA4MjcgMzkuNTY2NiA1NS40MzAyIDM3LjM2NjhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTYzLjg4MDMgNzMuMDYxQzU5LjcyODggNzMuMjA2NCA1NS41NTIzIDczLjI1NSA1MS4zNTA5IDczLjIwNjdDNTAuMjQxNyA3MS41MzgxIDQ5LjEwMDUgNjkuODg3IDQ3LjkyNzEgNjguMjUzMkM0Ni44NDUxIDY5Ljg4NCA0NS44MDA5IDcxLjUzNTEgNDQuNzk0OCA3My4yMDY3QzQwLjY5MDQgNzMuMjU1IDM2LjYxMSA3My4yMDY0IDMyLjU1NjcgNzMuMDYxQzM2LjM4NDkgNjcuMDIyMSA0MC4yNDU3IDYxLjAwMDMgNDQuMTM5MiA1NC45OTUzQzQ1LjcxNzMgNTMuMzQxOSA0Ny41ODcxIDUyLjkyOTIgNDkuNzQ4MyA1My43NTdDNTAuMzIyMyA1NC4wMzE3IDUwLjgzMjIgNTQuMzk2IDUxLjI3OCA1NC44NDk3QzU1LjUxMDkgNjAuOTAyNyA1OS43MTE3IDY2Ljk3MzIgNjMuODgwMyA3My4wNjFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE1LjM2NTIgNzMuMDYxMUMxOC45ODI1IDczLjIwNjUgMjIuNjI0NyA3My4yNTUgMjYuMjkyMSA3My4yMDY3QzIyLjYyNSA3My40MDA0IDE4LjkzNDIgNzMuNDAwNCAxNS4yMTk1IDczLjIwNjdDMTUuMjM3NiA3My4xMTgzIDE1LjI4NjEgNzMuMDY5OCAxNS4zNjUyIDczLjA2MTFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMyLjU1NjggNzMuMDYxMUMzNi42MTExIDczLjIwNjUgNDAuNjkwNCA3My4yNTUgNDQuNzk0OCA3My4yMDY3QzQwLjY5MDcgNzMuNDAwNCAzNi41NjI5IDczLjQwMDQgMzIuNDExMSA3My4yMDY3QzMyLjQyOTIgNzMuMTE4MyAzMi40Nzc3IDczLjA2OTggMzIuNTU2OCA3My4wNjExWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik02My44ODAzIDczLjA2MTFDNjMuOTU5NCA3My4wNjk4IDY0LjAwNzkgNzMuMTE4MyA2NC4wMjYgNzMuMjA2N0M1OS43NzcxIDczLjQwMDUgNTUuNTUyIDczLjQwMDUgNTEuMzUwOSA3My4yMDY3QzU1LjU1MjMgNzMuMjU1IDU5LjcyODkgNzMuMjA2NSA2My44ODAzIDczLjA2MTFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K",
|
||||
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iNDgiIGN5PSI0OCIgcj0iNDgiIGZpbGw9IiM4ODg4ODgiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yNi4yOTIyIDczLjIwNjdDMjIuNjI0OCA3My4yNTUgMTguOTgyNSA3My4yMDY1IDE1LjM2NTMgNzMuMDYxMUMyNi43NTIgNTUuMDQ4IDM4LjE0MDIgMzcuMDMwOSA0OS41Mjk5IDE5LjAwOTdDNTEuMTYzNSAxNy4zMjkzIDUyLjk4NDcgMTcuMTEwNyA1NC45OTMzIDE4LjM1NDFDNTcuNDkxIDIwLjQ1NTggNTguMjQzOCAyMy4wNTM5IDU3LjI1MTUgMjYuMTQ4NUM1NS40OTEzIDI5LjEzNTIgNTMuNjk0NSAzMi4wOTc1IDUxLjg2MDkgMzUuMDM1N0M0NC4xODc4IDQ3LjE3NjYgMzYuNTE0OSA1OS4zMTc2IDI4Ljg0MTcgNzEuNDU4NUMyOC4yODYxIDcyLjQ3NjMgMjcuNDM2MyA3My4wNTkgMjYuMjkyMiA3My4yMDY3WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik01NS40MzAyIDM3LjM2NjhDNTUuNzU2NiAzNy41MjI0IDU2LjAyMzcgMzcuNzY1MSA1Ni4yMzE1IDM4LjA5NTJDNjQuMzYwNiA0OS44NjE1IDcyLjU0MzcgNjEuNTg5NyA4MC43ODA0IDczLjI3OTZDNzcuMTg2NyA3My4zNzY4IDczLjU5MzEgNzMuMzc2OCA2OS45OTkzIDczLjI3OTZDNjguOTc5MyA3My4wNDkgNjguMTI5NSA3Mi41MzkxIDY3LjQ0OTcgNzEuNzQ5OUM2Mi4zMzc5IDY0LjI5NDMgNTcuMTkwMiA1Ni44NjQgNTIuMDA2NSA0OS40NTkxQzUxLjQ2MjcgNDguNjcxNSA1MC45NzcyIDQ3Ljg0NTkgNTAuNTQ5NiA0Ni45ODI0QzUwLjM3MDggNDUuODY2NCA1MC42MTM3IDQ0Ljg0NjYgNTEuMjc4IDQzLjkyMjlDNTIuNjk4NiA0MS43NTE5IDU0LjA4MjcgMzkuNTY2NiA1NS40MzAyIDM3LjM2NjhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTYzLjg4MDMgNzMuMDYxQzU5LjcyODggNzMuMjA2NCA1NS41NTIzIDczLjI1NSA1MS4zNTA5IDczLjIwNjdDNTAuMjQxNyA3MS41MzgxIDQ5LjEwMDUgNjkuODg3IDQ3LjkyNzEgNjguMjUzMkM0Ni44NDUxIDY5Ljg4NCA0NS44MDA5IDcxLjUzNTEgNDQuNzk0OCA3My4yMDY3QzQwLjY5MDQgNzMuMjU1IDM2LjYxMSA3My4yMDY0IDMyLjU1NjcgNzMuMDYxQzM2LjM4NDkgNjcuMDIyMSA0MC4yNDU3IDYxLjAwMDMgNDQuMTM5MiA1NC45OTUzQzQ1LjcxNzMgNTMuMzQxOSA0Ny41ODcxIDUyLjkyOTIgNDkuNzQ4MyA1My43NTdDNTAuMzIyMyA1NC4wMzE3IDUwLjgzMjIgNTQuMzk2IDUxLjI3OCA1NC44NDk3QzU1LjUxMDkgNjAuOTAyNyA1OS43MTE3IDY2Ljk3MzIgNjMuODgwMyA3My4wNjFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE1LjM2NTIgNzMuMDYxMUMxOC45ODI1IDczLjIwNjUgMjIuNjI0NyA3My4yNTUgMjYuMjkyMSA3My4yMDY3QzIyLjYyNSA3My40MDA0IDE4LjkzNDIgNzMuNDAwNCAxNS4yMTk1IDczLjIwNjdDMTUuMjM3NiA3My4xMTgzIDE1LjI4NjEgNzMuMDY5OCAxNS4zNjUyIDczLjA2MTFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMyLjU1NjggNzMuMDYxMUMzNi42MTExIDczLjIwNjUgNDAuNjkwNCA3My4yNTUgNDQuNzk0OCA3My4yMDY3QzQwLjY5MDcgNzMuNDAwNCAzNi41NjI5IDczLjQwMDQgMzIuNDExMSA3My4yMDY3QzMyLjQyOTIgNzMuMTE4MyAzMi40Nzc3IDczLjA2OTggMzIuNTU2OCA3My4wNjExWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik02My44ODAzIDczLjA2MTFDNjMuOTU5NCA3My4wNjk4IDY0LjAwNzkgNzMuMTE4MyA2NC4wMjYgNzMuMjA2N0M1OS43NzcxIDczLjQwMDUgNTUuNTUyIDczLjQwMDUgNTEuMzUwOSA3My4yMDY3QzU1LjU1MjMgNzMuMjU1IDU5LjcyODkgNzMuMjA2NSA2My44ODAzIDczLjA2MTFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K",
|
||||
"name": "Test Metadata 0",
|
||||
"origin": {
|
||||
"location": "external",
|
||||
|
@ -443,7 +443,7 @@ exports[`loadAgenticConfig > metadata-0 1`] = `
|
|||
"slug": "free",
|
||||
},
|
||||
],
|
||||
"readme": "data:application/octet-stream;base64,IyBUZXN0IE1ldGFkYXRhCgpUaGlzIGlzIHRlc3RpbmcgKipyZWFkbWUgcmVuZGVyaW5nKiouCgojIyBNaXNjCgotIFsgXSBJdGVtIDEKLSBbIF0gSXRlbSAyCi0gW3hdIEl0ZW0gMwoKLS0tCgotIF9pdGFsaWNfCi0gKipib2xkKioKLSBbbGlua10oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbSkKLSBgY29kZWAKCiMjIENvZGUKCmBgYHRzCmNvbnN0IGEgPSAxCgpleHBvcnQgZnVuY3Rpb24gZm9vKCkgewogIGNvbnNvbGUubG9nKCdoZWxsbyB3b3JsZCcpCn0KYGBgCgojIyBJbWFnZXMKCiFbSW1hZ2VdKGh0dHBzOi8vcGxhY2Vob2xkLmNvLzYwMHg0MDApCg==",
|
||||
"readme": "data:text/markdown;base64,IyBUZXN0IE1ldGFkYXRhCgpUaGlzIGlzIHRlc3RpbmcgKipyZWFkbWUgcmVuZGVyaW5nKiouCgojIyBNaXNjCgotIFsgXSBJdGVtIDEKLSBbIF0gSXRlbSAyCi0gW3hdIEl0ZW0gMwoKLS0tCgotIF9pdGFsaWNfCi0gKipib2xkKioKLSBbbGlua10oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbSkKLSBgY29kZWAKCiMjIENvZGUKCmBgYHRzCmNvbnN0IGEgPSAxCgpleHBvcnQgZnVuY3Rpb24gZm9vKCkgewogIGNvbnNvbGUubG9nKCdoZWxsbyB3b3JsZCcpCn0KYGBgCgojIyBJbWFnZXMKCiFbSW1hZ2VdKGh0dHBzOi8vcGxhY2Vob2xkLmNvLzYwMHg0MDApCg==",
|
||||
"slug": "test-metadata-0",
|
||||
"toolConfigs": [],
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import type {
|
|||
ResolvedAgenticProjectConfig
|
||||
} from '@agentic/platform-types'
|
||||
|
||||
import type { UploadFileToStorageFn } from './types'
|
||||
import type { UploadFileUrlToStorageFn } from './types'
|
||||
import {
|
||||
parseAgenticProjectConfig,
|
||||
parseResolvedAgenticProjectConfig
|
||||
|
@ -22,7 +22,7 @@ export async function resolveAgenticProjectConfig(
|
|||
logger?: Logger
|
||||
cwd?: string
|
||||
label?: string
|
||||
uploadFileToStorage: UploadFileToStorageFn
|
||||
uploadFileUrlToStorage: UploadFileUrlToStorageFn
|
||||
}
|
||||
): Promise<ResolvedAgenticProjectConfig> {
|
||||
const config = parseAgenticProjectConfig(inputConfig)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import type { UploadFileToStorageFn } from './types'
|
||||
import type { UploadFileUrlToStorageFn } from './types'
|
||||
|
||||
export async function resolveMetadataFile(
|
||||
input: string | undefined,
|
||||
{
|
||||
label,
|
||||
uploadFileToStorage
|
||||
uploadFileUrlToStorage
|
||||
}: {
|
||||
label: string
|
||||
uploadFileToStorage: UploadFileToStorageFn
|
||||
uploadFileUrlToStorage: UploadFileUrlToStorageFn
|
||||
}
|
||||
): Promise<string | undefined> {
|
||||
if (!input) return
|
||||
|
@ -15,7 +15,7 @@ export async function resolveMetadataFile(
|
|||
try {
|
||||
const source = new URL(input)
|
||||
|
||||
return uploadFileToStorage(source.toString())
|
||||
return uploadFileUrlToStorage(source.toString())
|
||||
} catch {
|
||||
throw new Error(`Invalid "${label}" must be a public URL: "${input}"`)
|
||||
}
|
||||
|
|
|
@ -3,28 +3,28 @@ import type {
|
|||
ResolvedAgenticProjectConfig
|
||||
} from '@agentic/platform-types'
|
||||
|
||||
import type { UploadFileToStorageFn } from './types'
|
||||
import type { UploadFileUrlToStorageFn } from './types'
|
||||
import { resolveMetadataFile } from './resolve-metadata-file'
|
||||
|
||||
export async function resolveMetadataFiles(
|
||||
{ readme, icon }: Pick<AgenticProjectConfig, 'readme' | 'icon'>,
|
||||
{
|
||||
uploadFileToStorage
|
||||
uploadFileUrlToStorage
|
||||
}: {
|
||||
uploadFileToStorage: UploadFileToStorageFn
|
||||
uploadFileUrlToStorage: UploadFileUrlToStorageFn
|
||||
}
|
||||
): Promise<Pick<ResolvedAgenticProjectConfig, 'readme' | 'iconUrl'>> {
|
||||
if (readme) {
|
||||
readme = await resolveMetadataFile(readme, {
|
||||
label: 'readme',
|
||||
uploadFileToStorage
|
||||
uploadFileUrlToStorage
|
||||
})
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
icon = await resolveMetadataFile(icon, {
|
||||
label: 'icon',
|
||||
uploadFileToStorage
|
||||
uploadFileUrlToStorage
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
export type UploadFileToStorageFn = (source: string) => Promise<string>
|
||||
export type UploadFileUrlToStorageFn = (source: string) => Promise<string>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
||||
import { lookup as lookupMimeType } from 'mrmime'
|
||||
|
||||
export async function validateMetadataFile(
|
||||
input: string | undefined,
|
||||
{
|
||||
|
@ -25,7 +27,10 @@ export async function validateMetadataFile(
|
|||
// Not a URL; check if it's a local file path.
|
||||
const buffer = await readFile(path.resolve(cwd, input))
|
||||
|
||||
return `data:application/octet-stream;base64,${buffer.toString('base64')}`
|
||||
const mime =
|
||||
lookupMimeType(path.extname(input)) ?? 'application/octet-stream'
|
||||
|
||||
return `data:${mime};base64,${buffer.toString('base64')}`
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Invalid "${label}" (must be a URL or a path to a local file): "${input}"`
|
||||
|
|
690
pnpm-lock.yaml
690
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
1
todo.md
1
todo.md
|
@ -135,3 +135,4 @@
|
|||
- basic account page on website
|
||||
- edit name, profile photo, etc
|
||||
- **public project detail page metadata**
|
||||
- upload user images to agentic blob storage
|
||||
|
|
Ładowanie…
Reference in New Issue