diff --git a/src/apis/agents/schemas.gen.ts b/src/apis/agents/schemas.gen.ts index 86fa42c..4275e34 100644 --- a/src/apis/agents/schemas.gen.ts +++ b/src/apis/agents/schemas.gen.ts @@ -58,6 +58,18 @@ export const GetAgentResponseSchema = { title: 'GetAgentResponse' } as const; +export const GetAgentSecretMessageSchema = { + properties: { + message: { + type: 'string', + title: 'Message' + } + }, + type: 'object', + required: ['message'], + title: 'GetAgentSecretMessage' +} as const; + export const GetAgentSecretResponseSchema = { properties: { secret: { diff --git a/src/apis/agents/services.gen.ts b/src/apis/agents/services.gen.ts index abf1fe7..adccf1e 100644 --- a/src/apis/agents/services.gen.ts +++ b/src/apis/agents/services.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options, formDataBodySerializer } from '@hey-api/client-axios'; -import type { SetupAgentPostData, SetupAgentPostError, SetupAgentPostResponse, DeleteAgentDeleteData, DeleteAgentDeleteError, DeleteAgentDeleteResponse, GetAgentPublicInfoAgentAgentIdGetData, GetAgentPublicInfoAgentAgentIdGetError, GetAgentPublicInfoAgentAgentIdGetResponse, UpdateAgentAgentIdPutData, UpdateAgentAgentIdPutError, UpdateAgentAgentIdPutResponse, GetAgentSecretAgentAgentIdSecretGetData, GetAgentSecretAgentAgentIdSecretGetError, GetAgentSecretAgentAgentIdSecretGetResponse } from './types.gen'; +import type { SetupAgentPostData, SetupAgentPostError, SetupAgentPostResponse, DeleteAgentDeleteData, DeleteAgentDeleteError, DeleteAgentDeleteResponse, GetAgentPublicInfoAgentAgentIdGetData, GetAgentPublicInfoAgentAgentIdGetError, GetAgentPublicInfoAgentAgentIdGetResponse, UpdateAgentAgentIdPutData, UpdateAgentAgentIdPutError, UpdateAgentAgentIdPutResponse, GetAgentSecretAgentAgentIdSecretGetData, GetAgentSecretAgentAgentIdSecretGetError, GetAgentSecretAgentAgentIdSecretGetResponse, GetAgentSecretMessageAgentAgentIdSecretMessageGetData, GetAgentSecretMessageAgentAgentIdSecretMessageGetError, GetAgentSecretMessageAgentAgentIdSecretMessageGetResponse } from './types.gen'; export const client = createClient(createConfig()); @@ -63,4 +63,15 @@ export const getAgentSecretAgentAgentIdSecretGet = (options: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/agent/{agent_id}/secret-message' + }); }; \ No newline at end of file diff --git a/src/apis/agents/types.gen.ts b/src/apis/agents/types.gen.ts index 5614452..087878f 100644 --- a/src/apis/agents/types.gen.ts +++ b/src/apis/agents/types.gen.ts @@ -17,6 +17,10 @@ export type GetAgentResponse = { last_update: number; }; +export type GetAgentSecretMessage = { + message: string; +}; + export type GetAgentSecretResponse = { secret: string; }; @@ -99,4 +103,14 @@ export type GetAgentSecretAgentAgentIdSecretGetData = { export type GetAgentSecretAgentAgentIdSecretGetResponse = (GetAgentSecretResponse); -export type GetAgentSecretAgentAgentIdSecretGetError = (HTTPValidationError); \ No newline at end of file +export type GetAgentSecretAgentAgentIdSecretGetError = (HTTPValidationError); + +export type GetAgentSecretMessageAgentAgentIdSecretMessageGetData = { + path: { + agent_id: string; + }; +}; + +export type GetAgentSecretMessageAgentAgentIdSecretMessageGetResponse = (GetAgentSecretMessage); + +export type GetAgentSecretMessageAgentAgentIdSecretMessageGetError = (HTTPValidationError); \ No newline at end of file diff --git a/src/apis/subscriptions/schemas.gen.ts b/src/apis/subscriptions/schemas.gen.ts index bd1b13b..b00a49b 100644 --- a/src/apis/subscriptions/schemas.gen.ts +++ b/src/apis/subscriptions/schemas.gen.ts @@ -180,6 +180,50 @@ export const SubsPostRefreshSubscriptionsResponseSchema = { title: 'SubsPostRefreshSubscriptionsResponse' } as const; +export const SubscriptionSchema = { + properties: { + id: { + type: 'string', + title: 'Id' + }, + type: { + '$ref': '#/components/schemas/SubscriptionType' + }, + provider: { + '$ref': '#/components/schemas/SubscriptionProvider' + }, + started_at: { + type: 'integer', + title: 'Started At' + }, + ended_at: { + type: 'integer', + title: 'Ended At' + }, + is_active: { + type: 'boolean', + title: 'Is Active' + }, + provider_data: { + type: 'object', + title: 'Provider Data' + }, + account: { + '$ref': '#/components/schemas/SubscriptionAccount' + }, + tags: { + items: { + type: 'string' + }, + type: 'array', + title: 'Tags' + } + }, + type: 'object', + required: ['id', 'type', 'provider', 'started_at', 'is_active', 'provider_data', 'account', 'tags'], + title: 'Subscription' +} as const; + export const SubscriptionAccountSchema = { properties: { address: { diff --git a/src/apis/subscriptions/services.gen.ts b/src/apis/subscriptions/services.gen.ts index fcaa9db..01490fe 100644 --- a/src/apis/subscriptions/services.gen.ts +++ b/src/apis/subscriptions/services.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-axios'; -import type { GetUserSubscriptionsSubscriptionsGetData, GetUserSubscriptionsSubscriptionsGetError, GetUserSubscriptionsSubscriptionsGetResponse, SubscribeHoldSubscriptionPostData, SubscribeHoldSubscriptionPostError, SubscribeHoldSubscriptionPostResponse, UnsubscribeHoldSubscriptionDeleteData, UnsubscribeHoldSubscriptionDeleteError, UnsubscribeHoldSubscriptionDeleteResponse, RefreshActiveHoldSubscriptionsHoldRefreshPostError, RefreshActiveHoldSubscriptionsHoldRefreshPostResponse, HoldSubscriptionMessagesHoldMessageGetData, HoldSubscriptionMessagesHoldMessageGetError, HoldSubscriptionMessagesHoldMessageGetResponse, RefreshSubsRefreshPostError, RefreshSubsRefreshPostResponse, SubscribeVouchersSubscriptionPostData, SubscribeVouchersSubscriptionPostError, SubscribeVouchersSubscriptionPostResponse, CancelVouchersSubscriptionsVouchersSubscriptionDeleteData, CancelVouchersSubscriptionsVouchersSubscriptionDeleteError, CancelVouchersSubscriptionsVouchersSubscriptionDeleteResponse, RefreshActiveVouchersSubscriptionsVouchersRefreshPostError, RefreshActiveVouchersSubscriptionsVouchersRefreshPostResponse } from './types.gen'; +import type { GetUserSubscriptionsSubscriptionsGetData, GetUserSubscriptionsSubscriptionsGetError, GetUserSubscriptionsSubscriptionsGetResponse, GetSubscriptionSubscriptionsSubscriptionIdGetData, GetSubscriptionSubscriptionsSubscriptionIdGetError, GetSubscriptionSubscriptionsSubscriptionIdGetResponse, SubscribeHoldSubscriptionPostData, SubscribeHoldSubscriptionPostError, SubscribeHoldSubscriptionPostResponse, UnsubscribeHoldSubscriptionDeleteData, UnsubscribeHoldSubscriptionDeleteError, UnsubscribeHoldSubscriptionDeleteResponse, RefreshActiveHoldSubscriptionsHoldRefreshPostError, RefreshActiveHoldSubscriptionsHoldRefreshPostResponse, HoldSubscriptionMessagesHoldMessageGetData, HoldSubscriptionMessagesHoldMessageGetError, HoldSubscriptionMessagesHoldMessageGetResponse, RefreshSubsRefreshPostError, RefreshSubsRefreshPostResponse, SubscribeVouchersSubscriptionPostData, SubscribeVouchersSubscriptionPostError, SubscribeVouchersSubscriptionPostResponse, CancelVouchersSubscriptionsVouchersSubscriptionDeleteData, CancelVouchersSubscriptionsVouchersSubscriptionDeleteError, CancelVouchersSubscriptionsVouchersSubscriptionDeleteResponse, RefreshActiveVouchersSubscriptionsVouchersRefreshPostError, RefreshActiveVouchersSubscriptionsVouchersRefreshPostResponse } from './types.gen'; export const client = createClient(createConfig()); @@ -15,6 +15,17 @@ export const getUserSubscriptionsSubscriptionsGet = (options: Options) => { + return (options?.client ?? client).get({ + ...options, + url: '/subscriptions/{subscription_id}' + }); +}; + /** * Subscribe * Subscribe to a plan diff --git a/src/apis/subscriptions/types.gen.ts b/src/apis/subscriptions/types.gen.ts index d6c50fd..b9fc345 100644 --- a/src/apis/subscriptions/types.gen.ts +++ b/src/apis/subscriptions/types.gen.ts @@ -53,6 +53,20 @@ export type SubsPostRefreshSubscriptionsResponse = { cancelled_subscriptions: Array<(string)>; }; +export type Subscription = { + id: string; + type: SubscriptionType; + provider: SubscriptionProvider; + started_at: number; + ended_at?: number; + is_active: boolean; + provider_data: { + [key: string]: unknown; + }; + account: SubscriptionAccount; + tags: Array<(string)>; +}; + export type SubscriptionAccount = { address: string; chain: SubscriptionChain; @@ -126,6 +140,16 @@ export type GetUserSubscriptionsSubscriptionsGetResponse = (GetUserSubscriptions export type GetUserSubscriptionsSubscriptionsGetError = (HTTPValidationError); +export type GetSubscriptionSubscriptionsSubscriptionIdGetData = { + path: { + subscription_id: string; + }; +}; + +export type GetSubscriptionSubscriptionsSubscriptionIdGetResponse = (Subscription); + +export type GetSubscriptionSubscriptionsSubscriptionIdGetError = (HTTPValidationError); + export type SubscribeHoldSubscriptionPostData = { body: HoldPostSubscriptionBody; }; diff --git a/src/boot/axios.ts b/src/boot/axios.ts index c1a6ff4..692af2b 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -1,9 +1,13 @@ import { boot } from 'quasar/wrappers'; -import { client } from 'src/apis/subscriptions/services.gen'; +import { client as agentsClient } from 'src/apis/agents/services.gen'; +import { client as subscriptionsClient } from 'src/apis/subscriptions/services.gen'; import env from 'src/config/env'; export default boot(() => { - client.setConfig({ + subscriptionsClient.setConfig({ baseURL: env.LTAI_SUBSCRIPTIONS_API_URL, }); + agentsClient.setConfig({ + baseURL: env.LTAI_AGENTS_API_URL, + }); }); diff --git a/src/pages/Agents.vue b/src/pages/Agents.vue index 83aaeb3..e97e699 100644 --- a/src/pages/Agents.vue +++ b/src/pages/Agents.vue @@ -10,12 +10,26 @@

Manage your agents

+ +
+
+

Agent {{ agent.id }}

+ https://aleph.sh/vm/{{ agent.vm_hash }} +

Not yet deployed

+

Last update: {{ dayjs.unix(agent.last_update) }}

+

Secret: {{ agent.secret }}

+ Get secret +
+
@@ -23,5 +37,9 @@ diff --git a/src/stores/account.ts b/src/stores/account.ts index 60d9f80..7607946 100644 --- a/src/stores/account.ts +++ b/src/stores/account.ts @@ -1,9 +1,10 @@ import * as solana from '@solana/web3.js'; -import { getBalance } from '@wagmi/core'; +import { getBalance, signMessage as signWagmiMessage } from '@wagmi/core'; import { defineStore } from 'pinia'; +import { useWallet } from 'solana-wallets-vue'; import env from 'src/config/env'; import { config } from 'src/config/wagmi'; -import { AlephPersistentStorage } from 'src/utils/aleph-persistent-storage'; +import { AlephPersistentStorage, LIBERTAI_MESSAGE } from 'src/utils/aleph-persistent-storage'; import { useKnowledgeStore } from 'stores/knowledge'; import { useSettingsStore } from 'stores/settings'; import { useSubscriptionStore } from 'stores/subscription'; @@ -45,6 +46,21 @@ export const useAccountStore = defineStore('account', { await Promise.all([tokensStore.update(), knowledgeStore.load(), subscriptionsStore.load()]); }, + async signMessage(message: string): Promise<`0x${string}` | string> { + if (this.account === null) { + throw Error('No account'); + } + + switch (this.account.chain) { + case 'base': + return signWagmiMessage(config, { message: message }); + case 'solana': + const { signMessage: signSolanaMessage } = useWallet(); + const signature = await signSolanaMessage.value!(Buffer.from(message)); + return Buffer.from(signature).toString('base64'); + } + }, + async initAlephStorage() { const settingsStore = useSettingsStore(); @@ -52,9 +68,7 @@ export const useAccountStore = defineStore('account', { return; } - const hash = - settingsStore.signatureHash[this.account.address] ?? - (await AlephPersistentStorage.signMessage(this.account.chain)); + const hash = settingsStore.signatureHash[this.account.address] ?? (await this.signMessage(LIBERTAI_MESSAGE)); if (settingsStore.isSignatureHashStored) { settingsStore.signatureHash[this.account.address] = hash; } diff --git a/src/stores/agent.ts b/src/stores/agent.ts new file mode 100644 index 0000000..7c9edbd --- /dev/null +++ b/src/stores/agent.ts @@ -0,0 +1,86 @@ +import { defineStore } from 'pinia'; +import { + getAgentPublicInfoAgentAgentIdGet, + getAgentSecretAgentAgentIdSecretGet, + getAgentSecretMessageAgentAgentIdSecretMessageGet, +} from 'src/apis/agents'; +import { UIAgent } from 'src/types/agent'; +import { useAccountStore } from 'stores/account'; +import { useSubscriptionStore } from 'stores/subscription'; + +type AgentState = { + agents: UIAgent[]; + isLoaded: boolean; +}; + +export const useAgentStore = defineStore('agents', { + state: (): AgentState => ({ + agents: [], + isLoaded: false, + }), + actions: { + async load() { + const { subscriptions } = useSubscriptionStore(); + const { account } = useAccountStore(); + + if (account === null) { + this.isLoaded = true; + return; + } + + const activeAgentSubscriptions = subscriptions.filter((sub) => sub.is_active && sub.type === 'agent'); + + if (activeAgentSubscriptions.length === 0) { + this.isLoaded = true; + return; + } + + this.agents = ( + await Promise.all( + activeAgentSubscriptions.map(async (agentSubscription) => { + const agent = await getAgentPublicInfoAgentAgentIdGet({ + path: { agent_id: agentSubscription.id }, + }); + return agent.data; + }), + ) + ).filter((agent) => agent !== undefined); + console.log(this.agents); + + this.isLoaded = true; + }, + + async getAgentSecret(agentId: string) { + const { signMessage } = useAccountStore(); + + const messageResponse = await getAgentSecretMessageAgentAgentIdSecretMessageGet({ path: { agent_id: agentId } }); + + if (messageResponse.data === undefined) { + throw new Error( + messageResponse.error.detail?.toString() ?? 'Unable to fetch the message to sign to get the agent secret', + ); + } + + const signature = await signMessage(messageResponse.data.message); + + const secretResponse = await getAgentSecretAgentAgentIdSecretGet({ + path: { agent_id: agentId }, + query: { + signature, + }, + }); + + // TODO: handle errors in the call to this function + if (secretResponse.data === undefined) { + throw new Error(secretResponse.error.detail?.toString() ?? 'Unable to get the agent secret'); + } + + this.agents = this.agents.map((agent) => { + if (agent.id === agentId) { + return { ...agent, secret: secretResponse.data.secret }; + } + return agent; + }); + }, + }, +}); diff --git a/src/stores/subscription.ts b/src/stores/subscription.ts index 0a54ba2..955dedc 100644 --- a/src/stores/subscription.ts +++ b/src/stores/subscription.ts @@ -1,4 +1,3 @@ -import { getAccount, signMessage } from '@wagmi/core'; import { defineStore } from 'pinia'; import { BaseSubscription, @@ -7,8 +6,8 @@ import { subscribeHoldSubscriptionPost, SubscriptionType, } from 'src/apis/subscriptions'; -import { config } from 'src/config/wagmi'; import { useAccountStore } from 'stores/account'; +import { useAgentStore } from 'stores/agent'; type SubscriptionState = { subscriptions: BaseSubscription[]; @@ -23,6 +22,7 @@ export const useSubscriptionStore = defineStore('subscriptions', { actions: { async load() { const { account } = useAccountStore(); + const agentStore = useAgentStore(); if (account === null) { return; @@ -37,13 +37,13 @@ export const useSubscriptionStore = defineStore('subscriptions', { this.subscriptions = response.data?.subscriptions ?? []; this.isLoaded = true; + agentStore.load().then(); }, async holdSubscribe(subscriptionType: SubscriptionType) { - const account = getAccount(config); - const address = account.address; + const { account, signMessage } = useAccountStore(); - if (address === undefined) { + if (account === null) { return; } @@ -58,13 +58,13 @@ export const useSubscriptionStore = defineStore('subscriptions', { } const messageToSign = messagesResponse.data.subscribe_message; - const hash = await signMessage(config, { message: messageToSign }); + const hash = await signMessage(messageToSign); const subscriptionResponse = await subscribeHoldSubscriptionPost({ body: { signature: hash, type: subscriptionType, - account: { chain: 'base', address }, + account: { chain: 'base', address: account.address }, }, }); console.log(subscriptionResponse); diff --git a/src/types/agent.ts b/src/types/agent.ts new file mode 100644 index 0000000..59ac396 --- /dev/null +++ b/src/types/agent.ts @@ -0,0 +1,3 @@ +import { GetAgentResponse } from 'src/apis/agents'; + +export type UIAgent = GetAgentResponse & { secret?: string }; diff --git a/src/utils/aleph-persistent-storage.ts b/src/utils/aleph-persistent-storage.ts index 369438a..c756ebc 100644 --- a/src/utils/aleph-persistent-storage.ts +++ b/src/utils/aleph-persistent-storage.ts @@ -8,13 +8,7 @@ import { import { AuthenticatedAlephHttpClient } from '@aleph-sdk/client'; import { ItemType } from '@aleph-sdk/message'; import { getAccountFromProvider as getSolAccountFromProvider, SOLAccount } from '@aleph-sdk/solana'; -import { - type Config, - getConnections, - getConnectorClient, - signMessage as signWagmiMessage, - switchChain, -} from '@wagmi/core'; +import { type Config, getConnections, getConnectorClient, switchChain } from '@wagmi/core'; import { base } from '@wagmi/vue/chains'; import { PrivateKey } from 'eciesjs'; import { providers } from 'ethers'; @@ -34,7 +28,7 @@ import web3 from 'web3'; // Aleph keys and channels settings const SECURITY_AGGREGATE_KEY = 'security'; -const MESSAGE = 'LibertAI'; +export const LIBERTAI_MESSAGE = 'LibertAI'; const LIBERTAI_GENERAL_CHANNEL = 'libertai'; const LIBERTAI_UI_CHANNEL = 'libertai-chat-ui'; const LIBERTAI_SETTINGS_KEY = `${LIBERTAI_UI_CHANNEL}-settings`; @@ -67,17 +61,6 @@ export class AlephPersistentStorage { private encryptionPrivateKey: PrivateKey, ) {} - static async signMessage(chain: AccountChain): Promise<`0x${string}` | string> { - switch (chain) { - case 'base': - return signWagmiMessage(config, { message: MESSAGE }); - case 'solana': - const { signMessage: signSolanaMessage } = useWallet(); - const signature = await signSolanaMessage.value!(Buffer.from(MESSAGE)); - return Buffer.from(signature).toString('base64'); - } - } - static async initialize(hash: string, chain: AccountChain) { const privateKey = web3.utils.sha3(hash);