Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions .claude/skills/agentstack-client-sdk-essentials/SKILL.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
---
name: agentstack-client-sdk-essentials
description: Basic implementation guidelines how to work with Agent Stack client SDK
---

Custom GUI integration with Agent Stack requires dependency on both [A2A](https://a2a-protocol.org/latest/specification) as well as [Agent Stack](https://agentstack.beeai.dev/llms.txt).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The link for 'Agent Stack' points to a llms.txt file. This seems incorrect for a general reference to the Agent Stack project. It would be more helpful for users if this link pointed to the main project or documentation page.

Suggested change
Custom GUI integration with Agent Stack requires dependency on both [A2A](https://a2a-protocol.org/latest/specification) as well as [Agent Stack](https://agentstack.beeai.dev/llms.txt).
Custom GUI integration with Agent Stack requires dependency on both [A2A](https://a2a-protocol.org/latest/specification) as well as [Agent Stack](https://agentstack.beeai.dev/).


You need to install both:

```bash
pnpm add @a2a-js/sdk agentstack-sdk
```

## Get list of available agents

You need to know the agent UUID in advance. The easiest way to obtain it is via Agent Stack API client which is a wrapper around Agent Stack server API.

Agents are also called Providers.

```ts
import { buildApiClient } from 'agentstack-sdk';

const agentstack = buildApiClient({ baseUrl: 'http://localhost:8334' });

const providers = await agentstack.listProviders({
query: {}
})

if (providers.ok) {
providers.data.items.forEach(provider => {
console.log(`The agent is named ${provider.agent_card.name} with id ${provider.id}`)
})
}
```

## Create Context and Token

Before creating A2A client, you must create a context (session) in Agent Stack. The context ID is used to correlate all communication throughout the conversation. It is also used for A2A client to communicate with the agents.

```ts
import { buildApiClient } from 'agentstack-sdk';

const agentstack = buildApiClient({ baseUrl: 'http://localhost:8334' });

const agentId = '548cd604-ce87-4ca4-b988-68357ca4cc40'

const context = await agentstack.createContext(agentId);

const { token } = await agentstack.createContextToken({
contextId: context.id,

globalPermissions: { llm: ['*'], a2a_proxy: ['*'] },

contextPermissions: {
files: ['*'],
vector_stores: ['*'],
context_data: ['*'],
},
});

const contextId = context.id;
Comment on lines +47 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This code snippet has a few issues that will cause it to fail:

  1. The createContext function is called with a string agentId, but it expects an object, likely of the form { provider_id: agentId }.
  2. The return values of createContext and createContextToken are Result objects. You must check if the operation was successful (e.g., result.ok) before trying to access the data property.
  3. Because context will hold the Result object from createContext, context.id will be undefined, causing an error in the createContextToken call.

Here is a corrected version of this block for your reference:

const contextResult = await agentstack.createContext({ provider_id: agentId });
if (!contextResult.ok) {
  throw contextResult.error;
}
const context = contextResult.data;

const tokenResult = await agentstack.createContextToken({
    contextId: context.id,
    globalPermissions: { llm: ['*'], a2a_proxy: ['*'] },
    contextPermissions: {
        files: ['*'],
        vector_stores: ['*'],
        context_data: ['*'],
    },
});
if (!tokenResult.ok) {
  throw tokenResult.error;
}
const { token } = tokenResult.data;

const contextId = context.id;

```

The `contextId` from Agent Stack MUST be used in all A2A messages. Do not generate your own UUID.

## Create A2A Client

Agent Stack SDK extends [A2A - Agent to Agent](https://a2a-protocol.org/latest/specification) protocol.

You need to create the client with a caveat that we need to provide fetch implemneation that packs in context token as authorization header.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a typo in the word 'implementation'.

Suggested change
You need to create the client with a caveat that we need to provide fetch implemneation that packs in context token as authorization header.
You need to create the client with a caveat that we need to provide fetch implementation that packs in context token as authorization header.


Agent Stack SDK exposes helper to build the fetch `createAuthenticatedFetch`

```ts
import {
ClientFactory,
ClientFactoryOptions,
DefaultAgentCardResolver,
JsonRpcTransportFactory,
} from '@a2a-js/sdk/client';
import { createAuthenticatedFetch } from "agentstack-sdk";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import statements in this file use a mix of single and double quotes. For consistency, it's best to stick to one style. The prevailing style in this file seems to be single quotes.

Suggested change
import { createAuthenticatedFetch } from "agentstack-sdk";
import { createAuthenticatedFetch } from 'agentstack-sdk';


const agentstackUrl = 'http://localhost:8334'

const agentUrl = `${agentstackUrl}/api/v1/a2a/${agentId}/agent-card.json`;

const fetchImpl = createAuthenticatedFetch(token);

const factory = new ClientFactory(
ClientFactoryOptions.createFrom(ClientFactoryOptions.default, {
transports: [new JsonRpcTransportFactory({ fetchImpl })],
cardResolver: new DefaultAgentCardResolver({ fetchImpl }),
}),
);
const a2aClient = await factory.createFromUrl(agentUrl);
```

## Multi turn (chat) conversation with the agent

Using A2A client to achieve multi turn (Chat) conversation with agent is relatively simple.

```ts
import type {
Message,
TaskArtifactUpdateEvent,
TaskStatusUpdateEvent,
} from '@a2a-js/sdk';

const conversationHistory: Message[] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The conversationHistory array is declared but never used in the code example. This could be confusing for readers. Please consider either removing it or demonstrating how it could be used to maintain conversation state.


const clientPrompt = 'This is the initial message from user'

const clientMessage: Message = {
messageId: crypto.randomUUID(),
role: 'user',
parts: [{ kind: 'text', text: clientPrompt }],
kind: 'message',
contextId,
metadata: {}
};

const messageStream = a2aClient.sendMessageStream({ message: clientMessage });

function isStatusUpdate(event: unknown): event is TaskStatusUpdateEvent {
return (
typeof event === 'object' &&
event !== null &&
'kind' in event &&
(event as { kind: string }).kind === 'status-update'
);
}

let agentReply = ''
for await (const event of messageStream) {
if (isStatusUpdate(event) && event.status?.message?.parts) {
for (const part of event.status.message.parts) {
if (part.kind === 'text') {
agentReply += part.text
}
}
}
}

console.log(`Client prompted ${clientPrompt} and agent responded with ${agentReply})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a typo in the console.log template literal. It includes an extra closing parenthesis ) inside the string, which will be printed in the output.

Suggested change
console.log(`Client prompted ${clientPrompt} and agent responded with ${agentReply})
console.log(`Client prompted ${clientPrompt} and agent responded with ${agentReply}`)

```

## Agent Stack Extensions

Agent Stack extends A2A with extensions. They are advertised via Agent Card and supplied via Message metadata.

Agents declare demands via agent card extensions. Client fulfills demands using dependency injection:

1. Fetch agent card from `/.well-known/agent-card.json`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line states that the agent card should be fetched from /.well-known/agent-card.json. However, the code example on line 85 uses a different path: ${agentstackUrl}/api/v1/a2a/${agentId}/agent-card.json. This inconsistency can be confusing. Please update the documentation to be consistent with the code example.

2. Figure out the demands in the Agent Card
3. Fulfill the demands in the client
4. Assemble and send the fulfillment via Message metadata.

### Example of how fulfill LLM extension
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a minor grammatical error in the heading. It should be 'how to fulfill'.

Suggested change
### Example of how fulfill LLM extension
### Example of how to fulfill LLM extension


```ts
import {
buildApiClient,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The buildApiClient function is imported but not used in this section's code examples. To avoid confusion, it's best to remove unused imports.

handleAgentCard,
buildLLMExtensionFulfillmentResolver,
type Fulfillments,
} from 'agentstack-sdk';

const agentCard = await a2aClient.getAgentCard();

const { resolveMetadata, demands } = handleAgentCard(agentCard);

const fulfillments: Fulfillments = {

getContextToken: () => token,

llm: async (demand) => {
return {
llm_fulfillments: Object.entries(demand.llm_demands).reduce((memo, [demandKey]) => {
return {
...memo,
[demandKey]: {
api_model: 'gpt5',
api_base: 'http://openai-endpoint',
api_key: 'API_KEY'
}
}

}, {})
Comment on lines +178 to +188
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of Object.entries with reduce to build the llm_fulfillments object can be simplified. Using Object.fromEntries with Object.keys().map() is a more modern and often more readable approach for this pattern.

Suggested change
llm_fulfillments: Object.entries(demand.llm_demands).reduce((memo, [demandKey]) => {
return {
...memo,
[demandKey]: {
api_model: 'gpt5',
api_base: 'http://openai-endpoint',
api_key: 'API_KEY'
}
}
}, {})
llm_fulfillments: Object.fromEntries(
Object.keys(demand.llm_demands).map((demandKey) => [
demandKey,
{
api_model: 'gpt5',
api_base: 'http://openai-endpoint',
api_key: 'API_KEY'
}
])
)

}
}
};

const metadata = await resolveMetadata(fulfillments);

const message: Message = {
messageId: crypto.randomUUID(),
role: 'user',
parts: [{ kind: 'text', text: 'Message content' }],
kind: 'message',
contextId,

metadata,
};

const stream = a2aClient.sendMessageStream({ message });

```

The Agent Stack provides LLM OpenAI compatible service that can be easily used and automatically resolved.

```ts
import { buildLLMExtensionFulfillmentResolver } from 'agentstack-sdk';

if (demands.llmDemands) {
fulfillments.llm = buildLLMExtensionFulfillmentResolver(agentstack, token);
}
```
16 changes: 16 additions & 0 deletions .claude/skills/agentstack-overview/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: agentstack-overview
description: Provides basic insight into what the main goal of Agent Stack is, how it's structured, and how it can be used.
---

## Agent Stack Goal

Agent Stack is a platform that provides infrastructure for developing and running AI agents. Agent builders can wrap their agent code with the Agent Stack SDK. The SDK creates a thin HTTP wrapper that exposes the [A2A - Agent to Agent](https://a2a-protocol.org/latest/specification) protocol, which is enhanced with custom [extensions](https://a2a-protocol.org/latest/topics/extensions/).

The exposed HTTP server is then registered with the AgentStack server. The Agent Stack UI then provides a GUI (or CLI) to run the agent.

## Agent Stack Personas

### Agent Builder

Typically a Python developer who either has an existing agent implemented in any framework (e.g., BeeAI, LangGraph, CrewAI) or is building an agent from scratch. This person wants to focus on building the agent, not on the interface, integration, or authentication. Agent Stack provides a quick and functional UI for their agents that they can use for local development, testing, or sharing with others.
Loading