Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/composite-actions/cypress-e2e/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ runs:
done

# --- Phase 5: Seed + test ---
- name: Push Weather Example Project
- name: Push Activities Planner Project
shell: bash
env:
INKEEP_API_KEY: ${{ env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET }}
Expand All @@ -99,7 +99,7 @@ runs:
attempt=1
while [ $attempt -le $max_attempts ]; do
echo "Push attempt $attempt/$max_attempts..."
if cd agents-cookbook/template-projects && npx inkeep push --project weather-project; then
if cd agents-cookbook/template-projects && npx inkeep push --project activities-planner; then
echo "Push succeeded on attempt $attempt"
break
fi
Expand Down
18 changes: 9 additions & 9 deletions agents-cli/src/__tests__/utils/templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ describe('Template Utils', () => {
it('should handle complete workflow from template fetch to clone', async () => {
// Mock getAvailableTemplates
const mockResponse = [
{ name: 'weather', type: 'dir' },
{ name: 'activities-planner', type: 'dir' },
{ name: 'chatbot', type: 'dir' },
];

Expand All @@ -212,14 +212,14 @@ describe('Template Utils', () => {

// Test complete workflow
const templates = await getAvailableTemplates('template-projects', undefined);
expect(templates).toContain('weather');
expect(templates).toContain('activities-planner');

await cloneTemplate(
'https://github.com/inkeep/agents/agents-cookbook/template-projects/weather',
'./weather-project'
'https://github.com/inkeep/agents/agents-cookbook/template-projects/activities-planner',
'./activities-planner'
);

expect(mockEmitter.clone).toHaveBeenCalledWith('./weather-project');
expect(mockEmitter.clone).toHaveBeenCalledWith('./activities-planner');
});

it('should handle concurrent template operations', async () => {
Expand All @@ -229,16 +229,16 @@ describe('Template Utils', () => {

const clonePromises = [
cloneTemplate(
'https://github.com/inkeep/agents/agents-cookbook/template-projects/weather',
'./weather1'
'https://github.com/inkeep/agents/agents-cookbook/template-projects/activities-planner',
'./planner1'
),
cloneTemplate(
'https://github.com/inkeep/agents/agents-cookbook/template-projects/chatbot',
'./chatbot1'
),
cloneTemplate(
'https://github.com/inkeep/agents/agents-cookbook/template-projects/weather',
'./weather2'
'https://github.com/inkeep/agents/agents-cookbook/template-projects/activities-planner',
'./planner2'
),
];

Expand Down
6 changes: 2 additions & 4 deletions agents-cli/src/utils/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export async function getAvailableTemplates(
const directories = [];
for (const item of items) {
const stat = await fs.stat(path.join(fullTemplatePath, item));
if (stat.isDirectory() && item !== 'weather-project') {
if (stat.isDirectory()) {
directories.push(item);
}
}
Expand Down Expand Up @@ -357,7 +357,5 @@ export async function getAvailableTemplates(
);
}

return contents
.filter((item: any) => item.type === 'dir' && item.name !== 'weather-project')
.map((item: any) => item.name);
return contents.filter((item: any) => item.type === 'dir').map((item: any) => item.name);
}
3 changes: 0 additions & 3 deletions agents-cookbook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
"knip": "knip --directory .. --workspace agents-cookbook --config agents-cookbook/knip.config.ts --dependencies",
"lint": "biome lint --error-on-warnings",
"lint:fix": "biome check --write",
"start:weather": "tsx weather-project/index.ts",
"dev:weather": "tsx --watch weather-project/index.ts",
"type-check": "tsc --noEmit",
"typecheck": "tsc --noEmit"
},
Expand All @@ -22,7 +20,6 @@
},
"devDependencies": {
"@types/node": "^22.18.12",
"tsx": "^4.20.6",
"typescript": "^5.9.3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { agent, subAgent } from '@inkeep/agents-sdk';
import { activities } from '../data-components/activities';
import { getCoordinates } from '../tools/get-coordinates';
import { getWeatherForecast } from '../tools/get-weather-forecast';

/**
* Activities Planner Solo Agent
*
* A fully offline variant of the activities planner that uses functionTool()
* instead of mcpTool() — no network access required.
*
* This agent works by:
* 1. Using the coordinates agent to get the coordinates of the specified location
* 2. Passing those coordinates to the weather forecast agent to get the weather forecast
* 3. Suggesting activities based on the weather conditions
*
* Example usage:
* "What are some good activities in Tokyo?"
* "What are some fun events in Boston?"
*/

const activitiesPlanner = subAgent({
id: 'activities-planner',
name: 'Activities planner',
description: 'Responsible for routing between the coordinates agent and weather forecast agent',
prompt:
'You are a helpful assistant. When the user asks about activities in a given location, first ask the coordinates agent for the coordinates, and then pass those coordinates to the weather forecast agent to get the weather forecast. Then based on the weather forecast, suggest good activities for the conditions.',
canDelegateTo: () => [weatherForecaster, coordinatesAgent],
dataComponents: () => [activities],
});

const weatherForecaster = subAgent({
id: 'weather-forecaster',
name: 'Weather forecaster',
description:
'This agent is responsible for taking in coordinates and returning the forecast for the weather at that location',
prompt:
'You are a helpful assistant responsible for taking in coordinates and returning the forecast for that location using your forecasting tool',
canUse: () => [getWeatherForecast],
});

const coordinatesAgent = subAgent({
id: 'get-coordinates-agent',
name: 'Coordinates agent',
description: 'Responsible for converting location or address into coordinates',
prompt:
'You are a helpful assistant responsible for converting location or address into coordinates using your coordinate converter tool',
canUse: () => [getCoordinates],
});

export const activitiesPlannerSoloAgent = agent({
id: 'activities-planner-solo',
name: 'Activities planner solo',
description:
'Plans activities for any location based on weather forecasts — fully offline, no network required',
defaultSubAgent: activitiesPlanner,
subAgents: () => [activitiesPlanner, weatherForecaster, coordinatesAgent],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { dataComponent } from '@inkeep/agents-sdk';
import { z } from 'zod';

export const activities = dataComponent({
id: 'activities',
name: 'Activities',
description: 'A list of activities',
props: z.object({
activities: z
.array(
z
.object({
title: z.string().describe('The main title of the event or activity category'),
category: z
.enum(['Festival', 'Fitness', 'Outdoor Activity', 'Market', 'Tour', 'Other'])
.describe('The type of event'),
description: z.string().describe('A brief description of the event'),
details: z
.object({
dates: z.string().optional().describe('The dates of the event'),
time: z.string().optional().describe('The time of the event'),
location: z.string().optional().describe('The location of the event'),
})
.optional()
.describe('Specific details like dates, time, and location'),
subItems: z
.array(z.string().describe('A sub-point or example'))
.optional()
.describe('A list of sub-points or examples, like different parks for hiking'),
})
.describe('A single activity or event entry')
)
.describe('The list of activities'),
}),
});
12 changes: 12 additions & 0 deletions agents-cookbook/template-projects/activities-planner-solo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { project } from '@inkeep/agents-sdk';
import { activitiesPlannerSoloAgent } from './agents/activities-planner-solo';
Comment thread
nick-inkeep marked this conversation as resolved.

export const activitiesPlannerSolo = project({
id: 'activities-planner-solo',
name: 'Activities planner solo',
description: 'Offline activities planner using local function tools — no network access required',
models: {
base: { model: 'anthropic/claude-sonnet-4-5' },
},
agents: () => [activitiesPlannerSoloAgent],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { functionTool } from '@inkeep/agents-sdk';

const CITY_COORDINATES: Record<string, { lat: number; lon: number }> = {
tokyo: { lat: 35.6762, lon: 139.6503 },
boston: { lat: 42.3601, lon: -71.0589 },
'new york': { lat: 40.7128, lon: -74.006 },
london: { lat: 51.5074, lon: -0.1278 },
paris: { lat: 48.8566, lon: 2.3522 },
'san francisco': { lat: 37.7749, lon: -122.4194 },
sydney: { lat: -33.8688, lon: 151.2093 },
};

export const getCoordinates = functionTool({
name: 'get-coordinates',
description:
'Convert a location name or address into geographic coordinates (latitude and longitude)',
inputSchema: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city name, address, or location to geocode',
},
},
required: ['location'],
},
execute: async ({ location }: { location: string }) => {
const key = location.toLowerCase().trim();
const match = CITY_COORDINATES[key];
if (match) {
return { latitude: match.lat, longitude: match.lon, name: location, found: true };
}
return {
latitude: 40.7128,
longitude: -74.006,
name: `${location} (defaulted to New York)`,
found: false,
};
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { functionTool } from '@inkeep/agents-sdk';

export const getWeatherForecast = functionTool({
name: 'get-weather-forecast',
description: 'Get an hourly weather forecast for the next 24 hours given geographic coordinates',
inputSchema: {
type: 'object',
properties: {
latitude: {
type: 'number',
description: 'Latitude of the location',
},
longitude: {
type: 'number',
description: 'Longitude of the location',
},
},
required: ['latitude', 'longitude'],
},
execute: async ({ latitude, longitude }: { latitude: number; longitude: number }) => {
const baseTemp = 18 + (Math.round(latitude * 0.1) % 15);
const conditions = ['Sunny', 'Partly cloudy', 'Cloudy', 'Light rain', 'Clear'];
const hours = Array.from({ length: 24 }, (_, i) => {
const hour = (8 + i) % 24;
const period = hour >= 12 ? 'PM' : 'AM';
const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
return {
time: `${displayHour}${period}`,
temperature: baseTemp + Math.round(Math.sin((i / 24) * Math.PI * 2) * 6),
conditions: conditions[i % conditions.length],
precipitation: i % 4 === 3 ? 30 : 0,
humidity: 50 + (i % 3) * 10,
windSpeed: 5 + (i % 5) * 3,
};
});

return {
location: { latitude, longitude },
forecast: hours,
summary: `Forecast generated for coordinates (${latitude.toFixed(2)}, ${longitude.toFixed(2)})`,
};
},
});
6 changes: 3 additions & 3 deletions agents-cookbook/template-projects/activities-planner/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { project } from '@inkeep/agents-sdk';
import { activitiesPlannerAgent } from './agents/activities-planner.js';
import { exaMcpTool } from './tools/exa-mcp.js';
import { weatherMcpTool } from './tools/weather-mcp.js';
import { activitiesPlannerAgent } from './agents/activities-planner';
import { exaMcpTool } from './tools/exa-mcp';
import { weatherMcpTool } from './tools/weather-mcp';

export const myProject = project({
id: 'activities-planner',
Expand Down
6 changes: 3 additions & 3 deletions agents-cookbook/template-projects/customer-support/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { project } from '@inkeep/agents-sdk';
import { customerSupport } from './agents/customer-support.js';
import { knowledgeBaseMcpTool } from './tools/knowledge-base-mcp.js';
import { zendeskMcpTool } from './tools/zendesk-mcp.js';
import { customerSupport } from './agents/customer-support';
import { knowledgeBaseMcpTool } from './tools/knowledge-base-mcp';
import { zendeskMcpTool } from './tools/zendesk-mcp';

export const myProject = project({
id: 'customer-support',
Expand Down
4 changes: 2 additions & 2 deletions agents-cookbook/template-projects/deep-research/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { project } from '@inkeep/agents-sdk';
import { deepResearchAgent } from './agents/deep-research.js';
import { firecrawlMcpTool } from './tools/firecrawl-mcp.js';
import { deepResearchAgent } from './agents/deep-research';
import { firecrawlMcpTool } from './tools/firecrawl-mcp';

export const myProject = project({
id: 'deep-research',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { subAgent } from '@inkeep/agents-sdk';
import { exaMcpTool } from '../../tools/exa-mcp.js';
import { exaMcpTool } from '../../tools/exa-mcp';

export const companyResearch = subAgent({
id: 'company-research',
Expand Down
4 changes: 2 additions & 2 deletions agents-cookbook/template-projects/meeting-prep/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { project } from '@inkeep/agents-sdk';
import { meetingAssistant } from './agents/meeting-assistant';
import { exaMcpTool } from './tools/exa-mcp.js';
import { exaMcpTool } from './tools/exa-mcp';
import { googleCalendarMcpTool } from './tools/google-calendar-mcp';

export const meetingPrep = project({
id: 'activities-planner',
id: 'meeting-prep',
name: 'Meeting prep',
description: 'Meeting prep project template',
agents: () => [meetingAssistant],
Expand Down
2 changes: 0 additions & 2 deletions agents-cookbook/template-projects/weather-project/.gitignore

This file was deleted.

This file was deleted.

Loading
Loading