@jaypie/mcp 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/suite.js +1 -1
- package/package.json +1 -1
- package/release-notes/constructs/1.2.18.md +12 -0
- package/release-notes/llm/1.2.5.md +25 -0
- package/release-notes/llm/1.2.6.md +55 -0
- package/skills/agents.md +18 -8
- package/skills/aws.md +232 -60
- package/skills/datadog.md +98 -64
- package/skills/dynamodb.md +37 -28
- package/skills/issues.md +55 -0
- package/skills/llm.md +381 -0
- package/skills/secrets.md +74 -18
- package/skills/skills.md +23 -0
- package/skills/tools-aws.md +148 -0
- package/skills/tools-datadog.md +190 -0
- package/skills/tools-dynamodb.md +140 -0
- package/skills/tools.md +19 -54
- package/skills/vocabulary.md +25 -0
- package/skills/topics.md +0 -116
- /package/skills/{writing.md → documentation.md} +0 -0
package/skills/dynamodb.md
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: DynamoDB patterns and queries
|
|
3
|
-
related: aws, cdk, models
|
|
2
|
+
description: DynamoDB code patterns, key design, and queries
|
|
3
|
+
related: aws, cdk, models, tools-dynamodb
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# DynamoDB Patterns
|
|
7
7
|
|
|
8
8
|
Best practices for DynamoDB with Jaypie applications.
|
|
9
9
|
|
|
10
|
-
## MCP
|
|
10
|
+
## MCP Tools
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
aws_dynamodb_describe_table - Get table schema and indexes
|
|
14
|
-
aws_dynamodb_query - Query by partition key (efficient)
|
|
15
|
-
aws_dynamodb_scan - Full table scan (use sparingly)
|
|
16
|
-
aws_dynamodb_get_item - Get single item by key
|
|
17
|
-
```
|
|
12
|
+
For interactive DynamoDB tools (query, scan, get-item, describe-table), see **tools-dynamodb**.
|
|
18
13
|
|
|
19
14
|
## Key Design
|
|
20
15
|
|
|
@@ -48,21 +43,7 @@ Use prefixed keys for multiple entity types:
|
|
|
48
43
|
| List user orders | pk = USER#123, sk begins_with ORDER# |
|
|
49
44
|
| Get specific order | pk = USER#123, sk = ORDER#2024-01-15#abc |
|
|
50
45
|
|
|
51
|
-
## Query
|
|
52
|
-
|
|
53
|
-
### Query via MCP
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
# Get user profile
|
|
57
|
-
aws_dynamodb_get_item --tableName "MyTable" --key '{"pk":{"S":"USER#123"},"sk":{"S":"PROFILE"}}'
|
|
58
|
-
|
|
59
|
-
# List user orders
|
|
60
|
-
aws_dynamodb_query --tableName "MyTable" \
|
|
61
|
-
--keyConditionExpression "pk = :pk AND begins_with(sk, :prefix)" \
|
|
62
|
-
--expressionAttributeValues '{":pk":{"S":"USER#123"},":prefix":{"S":"ORDER#"}}'
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Query in Code
|
|
46
|
+
## Query in Code
|
|
66
47
|
|
|
67
48
|
```typescript
|
|
68
49
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
@@ -98,10 +79,15 @@ const result = await client.send(new QueryCommand({
|
|
|
98
79
|
|
|
99
80
|
Query pending orders:
|
|
100
81
|
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
82
|
+
```typescript
|
|
83
|
+
const result = await client.send(new QueryCommand({
|
|
84
|
+
TableName: process.env.CDK_ENV_TABLE,
|
|
85
|
+
IndexName: "gsi1",
|
|
86
|
+
KeyConditionExpression: "gsi1pk = :status",
|
|
87
|
+
ExpressionAttributeValues: {
|
|
88
|
+
":status": "ORDER#pending",
|
|
89
|
+
},
|
|
90
|
+
}));
|
|
105
91
|
```
|
|
106
92
|
|
|
107
93
|
## CDK Table Definition
|
|
@@ -138,3 +124,26 @@ AWS_ACCESS_KEY_ID=local AWS_SECRET_ACCESS_KEY=local \
|
|
|
138
124
|
--endpoint-url http://127.0.0.1:8000
|
|
139
125
|
```
|
|
140
126
|
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
Mock DynamoDB in tests:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
133
|
+
import { vi } from "vitest";
|
|
134
|
+
|
|
135
|
+
vi.mock("@aws-sdk/client-dynamodb");
|
|
136
|
+
|
|
137
|
+
describe("OrderService", () => {
|
|
138
|
+
it("queries user orders", async () => {
|
|
139
|
+
vi.mocked(DynamoDBClient.prototype.send).mockResolvedValue({
|
|
140
|
+
Items: [{ pk: "USER#123", sk: "ORDER#abc" }],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const orders = await getOrders("123");
|
|
144
|
+
|
|
145
|
+
expect(orders).toHaveLength(1);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
package/skills/issues.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Submitting issues to Jaypie repositories
|
|
3
|
+
related: jaypie, agents
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jaypie Issues
|
|
7
|
+
|
|
8
|
+
## Repository
|
|
9
|
+
|
|
10
|
+
Submit issues to: **github.com/finlaysonstudio/jaypie**
|
|
11
|
+
|
|
12
|
+
## Agent Guidelines
|
|
13
|
+
|
|
14
|
+
**CRITICAL: Never submit issues without explicit user approval.**
|
|
15
|
+
|
|
16
|
+
Before creating an issue:
|
|
17
|
+
1. Draft the issue title and body
|
|
18
|
+
2. Present the draft to the user
|
|
19
|
+
3. Wait for explicit approval ("yes", "submit it", etc.)
|
|
20
|
+
4. Only then use `mcp__github__issue_write` to create the issue
|
|
21
|
+
|
|
22
|
+
## Issue Format
|
|
23
|
+
|
|
24
|
+
```markdown
|
|
25
|
+
**Title**: [Brief, descriptive title]
|
|
26
|
+
|
|
27
|
+
**Description**:
|
|
28
|
+
[What is the issue or feature request]
|
|
29
|
+
|
|
30
|
+
**Context**:
|
|
31
|
+
- Package: [affected package, e.g., `jaypie`, `@jaypie/express`]
|
|
32
|
+
- Version: [if known]
|
|
33
|
+
- Environment: [if relevant]
|
|
34
|
+
|
|
35
|
+
**Steps to Reproduce** (for bugs):
|
|
36
|
+
1. ...
|
|
37
|
+
2. ...
|
|
38
|
+
|
|
39
|
+
**Expected Behavior**:
|
|
40
|
+
[What should happen]
|
|
41
|
+
|
|
42
|
+
**Actual Behavior**:
|
|
43
|
+
[What actually happens]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Labels
|
|
47
|
+
|
|
48
|
+
Common labels: `bug`, `enhancement`, `documentation`, `question`
|
|
49
|
+
|
|
50
|
+
## When to Suggest Issues
|
|
51
|
+
|
|
52
|
+
- Bugs in Jaypie packages discovered during development
|
|
53
|
+
- Missing features that would benefit multiple projects
|
|
54
|
+
- Documentation gaps or errors
|
|
55
|
+
- API inconsistencies
|
package/skills/llm.md
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Using @jaypie/llm for unified LLM access
|
|
3
|
+
related: fabric, tools, tests
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @jaypie/llm
|
|
7
|
+
|
|
8
|
+
Unified interface for calling LLM providers with multi-turn conversations, tool calling, streaming, and structured output.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import Llm from "@jaypie/llm";
|
|
14
|
+
|
|
15
|
+
// Auto-detect provider from model name
|
|
16
|
+
const response = await Llm.operate("What is 2+2?", { model: "claude-sonnet-4" });
|
|
17
|
+
console.log(response.content); // "4"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Providers and Models
|
|
21
|
+
|
|
22
|
+
| Provider | Match Keywords | Default Model |
|
|
23
|
+
|----------|----------------|---------------|
|
|
24
|
+
| OpenAI | "openai", "gpt", /^o\d/ | gpt-5.1 |
|
|
25
|
+
| Anthropic | "anthropic", "claude", "haiku", "opus", "sonnet" | claude-sonnet-4-5 |
|
|
26
|
+
| Gemini | "gemini", "google" | gemini-3-pro-preview |
|
|
27
|
+
| OpenRouter | "openrouter" | z-ai/glm-4.7 |
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// Provider auto-detected from model
|
|
31
|
+
await Llm.operate(input, { model: "gpt-5.1" }); // OpenAI
|
|
32
|
+
await Llm.operate(input, { model: "claude-opus-4" }); // Anthropic
|
|
33
|
+
await Llm.operate(input, { model: "gemini-3" }); // Gemini
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Core Methods
|
|
37
|
+
|
|
38
|
+
### operate() - Multi-turn with Tools
|
|
39
|
+
|
|
40
|
+
The primary method for complex interactions:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const response = await Llm.operate(input, {
|
|
44
|
+
model: "gpt-5.1",
|
|
45
|
+
system: "You are a helpful assistant",
|
|
46
|
+
tools: toolkit,
|
|
47
|
+
turns: 5, // Allow up to 5 conversation turns
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Response structure
|
|
51
|
+
response.content; // string | object (structured output)
|
|
52
|
+
response.history; // LlmHistory - for follow-up calls
|
|
53
|
+
response.usage; // Token usage per turn
|
|
54
|
+
response.reasoning; // Extended thinking (if available)
|
|
55
|
+
response.status; // "completed" | "incomplete" | "in_progress"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### send() - Simple Completions
|
|
59
|
+
|
|
60
|
+
For single-shot text completions:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const response = await Llm.send("Explain REST APIs", {
|
|
64
|
+
model: "claude-sonnet-4",
|
|
65
|
+
system: "You are a technical writer",
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### stream() - Streaming Responses
|
|
70
|
+
|
|
71
|
+
For real-time output:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
for await (const chunk of Llm.stream("Tell me a story", { model: "gpt-5.1" })) {
|
|
75
|
+
switch (chunk.type) {
|
|
76
|
+
case "text":
|
|
77
|
+
process.stdout.write(chunk.content);
|
|
78
|
+
break;
|
|
79
|
+
case "tool_call":
|
|
80
|
+
console.log(`Tool: ${chunk.toolCall.name}`);
|
|
81
|
+
break;
|
|
82
|
+
case "done":
|
|
83
|
+
console.log("Usage:", chunk.usage);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Tool Calling
|
|
90
|
+
|
|
91
|
+
### Define Tools with Toolkit
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import Llm, { Toolkit } from "@jaypie/llm";
|
|
95
|
+
|
|
96
|
+
const toolkit = new Toolkit([
|
|
97
|
+
{
|
|
98
|
+
name: "get_weather",
|
|
99
|
+
description: "Get current weather for a city",
|
|
100
|
+
type: "function",
|
|
101
|
+
parameters: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
city: { type: "string", description: "City name" },
|
|
105
|
+
},
|
|
106
|
+
required: ["city"],
|
|
107
|
+
},
|
|
108
|
+
call: async ({ city }) => {
|
|
109
|
+
// Actual implementation
|
|
110
|
+
return { temp: 72, conditions: "sunny" };
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
const response = await Llm.operate("What's the weather in NYC?", {
|
|
116
|
+
model: "gpt-5.1",
|
|
117
|
+
tools: toolkit,
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Built-in Tools
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import Llm, { tools, JaypieToolkit } from "@jaypie/llm";
|
|
125
|
+
|
|
126
|
+
// Use individual tools
|
|
127
|
+
const response = await Llm.operate("Roll 2d6", {
|
|
128
|
+
model: "gpt-5.1",
|
|
129
|
+
tools, // Includes: random, roll, time, weather
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Or use the pre-configured toolkit
|
|
133
|
+
const toolkit = new JaypieToolkit();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Fabric Services as Tools
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { fabricLlmTool } from "@jaypie/fabric";
|
|
140
|
+
|
|
141
|
+
const greetTool = fabricLlmTool(greetService);
|
|
142
|
+
const toolkit = new Toolkit([greetTool]);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Structured Output
|
|
146
|
+
|
|
147
|
+
### Natural Schema
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const result = await Llm.operate("Extract contact info from: John Doe, john@example.com, 555-1234", {
|
|
151
|
+
model: "gpt-5.1",
|
|
152
|
+
format: {
|
|
153
|
+
name: String,
|
|
154
|
+
email: String,
|
|
155
|
+
phone: String,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
// result.content = { name: "John Doe", email: "john@example.com", phone: "555-1234" }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Zod Schema
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { z } from "zod";
|
|
165
|
+
|
|
166
|
+
const PersonSchema = z.object({
|
|
167
|
+
name: z.string(),
|
|
168
|
+
age: z.number(),
|
|
169
|
+
hobbies: z.array(z.string()),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const result = await Llm.operate("Parse: Alice is 30 and likes hiking and reading", {
|
|
173
|
+
model: "gpt-5.1",
|
|
174
|
+
format: PersonSchema,
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Conversation History
|
|
179
|
+
|
|
180
|
+
Continue conversations across calls:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const first = await Llm.operate("My name is Alice", { model: "gpt-5.1" });
|
|
184
|
+
|
|
185
|
+
const second = await Llm.operate("What's my name?", {
|
|
186
|
+
model: "gpt-5.1",
|
|
187
|
+
history: first.history,
|
|
188
|
+
});
|
|
189
|
+
// second.content = "Your name is Alice"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Input with Files and Images
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const response = await Llm.operate([
|
|
196
|
+
"Analyze these documents",
|
|
197
|
+
{ file: "report.pdf", bucket: "my-bucket" },
|
|
198
|
+
{ image: "chart.png" },
|
|
199
|
+
], {
|
|
200
|
+
model: "claude-sonnet-4",
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Placeholders and Data
|
|
205
|
+
|
|
206
|
+
Substitute data into prompts:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const response = await Llm.operate("Summarize the article about {{topic}}", {
|
|
210
|
+
model: "gpt-5.1",
|
|
211
|
+
data: { topic: "climate change" },
|
|
212
|
+
placeholders: { input: true },
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Lifecycle Hooks
|
|
217
|
+
|
|
218
|
+
Monitor and react to LLM operations:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const response = await Llm.operate(input, {
|
|
222
|
+
model: "gpt-5.1",
|
|
223
|
+
hooks: {
|
|
224
|
+
beforeEachModelRequest: ({ providerRequest }) => {
|
|
225
|
+
console.log("Sending request...");
|
|
226
|
+
},
|
|
227
|
+
afterEachModelResponse: ({ content, usage }) => {
|
|
228
|
+
console.log(`Tokens: ${usage.total}`);
|
|
229
|
+
},
|
|
230
|
+
beforeEachTool: ({ toolName, args }) => {
|
|
231
|
+
console.log(`Calling ${toolName} with`, args);
|
|
232
|
+
},
|
|
233
|
+
afterEachTool: ({ result, toolName }) => {
|
|
234
|
+
console.log(`${toolName} returned:`, result);
|
|
235
|
+
},
|
|
236
|
+
onToolError: ({ error, toolName }) => {
|
|
237
|
+
console.error(`${toolName} failed:`, error);
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Fallback Providers
|
|
244
|
+
|
|
245
|
+
Configure a chain of fallback providers that automatically retry failed calls when the primary provider fails:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Instance-level configuration
|
|
249
|
+
const llm = new Llm("anthropic", {
|
|
250
|
+
model: "claude-sonnet-4",
|
|
251
|
+
fallback: [
|
|
252
|
+
{ provider: "openai", model: "gpt-4o" },
|
|
253
|
+
{ provider: "gemini", model: "gemini-2.0-flash" },
|
|
254
|
+
],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Per-call override
|
|
258
|
+
const response = await llm.operate(input, {
|
|
259
|
+
fallback: [{ provider: "openai", model: "gpt-4o" }],
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Disable fallback for specific call
|
|
263
|
+
const response = await llm.operate(input, { fallback: false });
|
|
264
|
+
|
|
265
|
+
// Static method with fallback
|
|
266
|
+
const response = await Llm.operate(input, {
|
|
267
|
+
model: "claude-sonnet-4",
|
|
268
|
+
fallback: [{ provider: "openai", model: "gpt-4o" }],
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Fallback Response Metadata
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
response.provider; // Which provider handled the request
|
|
276
|
+
response.fallbackUsed; // true if a fallback was used
|
|
277
|
+
response.fallbackAttempts; // Number of providers tried (1 = primary only)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Instance Usage
|
|
281
|
+
|
|
282
|
+
For repeated calls with same configuration:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const llm = new Llm("anthropic", {
|
|
286
|
+
model: "claude-sonnet-4",
|
|
287
|
+
system: "You are a code reviewer",
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const review1 = await llm.operate(code1);
|
|
291
|
+
const review2 = await llm.operate(code2);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Environment Variables
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
OPENAI_API_KEY # Required for OpenAI
|
|
298
|
+
ANTHROPIC_API_KEY # Required for Anthropic
|
|
299
|
+
GOOGLE_API_KEY # Required for Gemini
|
|
300
|
+
OPENROUTER_API_KEY # Required for OpenRouter
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Keys are resolved via `getEnvSecret()` which supports AWS Secrets Manager.
|
|
304
|
+
|
|
305
|
+
## Error Handling
|
|
306
|
+
|
|
307
|
+
The package auto-retries rate limits and transient errors:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
try {
|
|
311
|
+
const response = await Llm.operate(input, { model: "gpt-5.1" });
|
|
312
|
+
if (response.status !== "completed") {
|
|
313
|
+
console.log("Incomplete:", response.error);
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
// Unrecoverable errors (auth, validation) throw
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Testing Pattern
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import { describe, expect, it, vi } from "vitest";
|
|
324
|
+
import Llm, { Toolkit } from "@jaypie/llm";
|
|
325
|
+
|
|
326
|
+
describe("LLM Integration", () => {
|
|
327
|
+
it("calls tool and returns response", async () => {
|
|
328
|
+
const mockTool = {
|
|
329
|
+
name: "calculate",
|
|
330
|
+
description: "Perform calculation",
|
|
331
|
+
type: "function",
|
|
332
|
+
parameters: { type: "object", properties: { expr: { type: "string" } } },
|
|
333
|
+
call: vi.fn().mockResolvedValue("42"),
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const toolkit = new Toolkit([mockTool]);
|
|
337
|
+
const response = await Llm.operate("Calculate 6*7", {
|
|
338
|
+
model: "gpt-5.1",
|
|
339
|
+
tools: toolkit,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(mockTool.call).toHaveBeenCalled();
|
|
343
|
+
expect(response.status).toBe("completed");
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Best Practices
|
|
349
|
+
|
|
350
|
+
1. **Use operate() over send()** - More flexible, handles tools and structure
|
|
351
|
+
2. **Set turns appropriately** - Default is 1; increase for tool-heavy workflows
|
|
352
|
+
3. **Provide clear tool descriptions** - LLMs use these to decide which tool to call
|
|
353
|
+
4. **Use structured output** - More reliable than parsing free text
|
|
354
|
+
5. **Track usage** - Monitor `response.usage` for cost management
|
|
355
|
+
6. **Handle incomplete status** - Check `response.status` for tool errors or limits
|
|
356
|
+
7. **Configure fallbacks** - Set up fallback providers for resilience against outages
|
|
357
|
+
|
|
358
|
+
## Common Options
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
interface LlmOperateOptions {
|
|
362
|
+
data?: Record<string, any>; // Placeholder substitution data
|
|
363
|
+
fallback?: LlmFallbackConfig[] | false; // Fallback provider chain
|
|
364
|
+
format?: JsonObject | ZodType; // Structured output schema
|
|
365
|
+
history?: LlmHistory; // Previous conversation
|
|
366
|
+
hooks?: LlmHooks; // Lifecycle callbacks
|
|
367
|
+
instructions?: string; // Additional instructions
|
|
368
|
+
model?: string; // Model override
|
|
369
|
+
system?: string; // System prompt
|
|
370
|
+
tools?: LlmTool[] | Toolkit; // Available tools
|
|
371
|
+
turns?: boolean | number; // Max conversation turns
|
|
372
|
+
user?: string; // User ID for logging
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
interface LlmFallbackConfig {
|
|
376
|
+
provider: string; // Provider name (e.g., "openai", "anthropic", "gemini")
|
|
377
|
+
model?: string; // Model to use (optional, uses provider default)
|
|
378
|
+
apiKey?: string; // API key (optional, uses environment variable)
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
package/skills/secrets.md
CHANGED
|
@@ -5,26 +5,52 @@ related: aws, cdk, variables
|
|
|
5
5
|
|
|
6
6
|
# Secret Management
|
|
7
7
|
|
|
8
|
-
Jaypie uses AWS Secrets Manager for secure credential storage.
|
|
8
|
+
Jaypie uses AWS Secrets Manager for secure credential storage, with environment-aware resolution that works seamlessly in both local development and Lambda.
|
|
9
9
|
|
|
10
10
|
## Basic Usage
|
|
11
11
|
|
|
12
|
+
Use `getEnvSecret` for environment-aware secret resolution:
|
|
13
|
+
|
|
12
14
|
```typescript
|
|
13
|
-
import {
|
|
15
|
+
import { getEnvSecret } from "jaypie";
|
|
14
16
|
|
|
15
|
-
const apiKey = await
|
|
16
|
-
const dbUri = await
|
|
17
|
+
const apiKey = await getEnvSecret("ANTHROPIC_API_KEY");
|
|
18
|
+
const dbUri = await getEnvSecret("MONGODB_URI");
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
### How getEnvSecret Works
|
|
22
|
+
|
|
23
|
+
`getEnvSecret` checks environment variables in order:
|
|
24
|
+
|
|
25
|
+
1. `SECRET_{name}` - If found, fetches from AWS Secrets Manager
|
|
26
|
+
2. `{name}_SECRET` - If found, fetches from AWS Secrets Manager
|
|
27
|
+
3. `{name}` - Returns direct value without AWS call
|
|
28
|
+
|
|
29
|
+
This allows the same code to work locally (with direct env values) and in Lambda (with secret references).
|
|
30
|
+
|
|
31
|
+
## Loading Multiple Secrets
|
|
32
|
+
|
|
33
|
+
Use `loadEnvSecrets` during handler initialization:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { loadEnvSecrets } from "jaypie";
|
|
37
|
+
|
|
38
|
+
// Load secrets and set in process.env
|
|
39
|
+
await loadEnvSecrets("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "MONGODB_URI");
|
|
40
|
+
|
|
41
|
+
// Now available as process.env.ANTHROPIC_API_KEY, etc.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## CDK Configuration
|
|
20
45
|
|
|
21
46
|
Reference secrets via environment variables in CDK:
|
|
22
47
|
|
|
23
48
|
```typescript
|
|
24
49
|
const handler = new JaypieLambda(this, "Handler", {
|
|
25
50
|
environment: {
|
|
26
|
-
|
|
27
|
-
|
|
51
|
+
// SECRET_ prefix triggers AWS Secrets Manager fetch
|
|
52
|
+
SECRET_MONGODB_URI: "my-project/mongodb-uri",
|
|
53
|
+
SECRET_API_KEY: "my-project/third-party-api-key",
|
|
28
54
|
},
|
|
29
55
|
});
|
|
30
56
|
```
|
|
@@ -32,10 +58,23 @@ const handler = new JaypieLambda(this, "Handler", {
|
|
|
32
58
|
In code:
|
|
33
59
|
|
|
34
60
|
```typescript
|
|
35
|
-
|
|
36
|
-
const mongoUri = await
|
|
61
|
+
// getEnvSecret sees SECRET_MONGODB_URI and fetches from Secrets Manager
|
|
62
|
+
const mongoUri = await getEnvSecret("MONGODB_URI");
|
|
37
63
|
```
|
|
38
64
|
|
|
65
|
+
## Direct Secret Access
|
|
66
|
+
|
|
67
|
+
Use `getSecret` when you need to fetch by exact AWS secret name:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { getSecret } from "jaypie";
|
|
71
|
+
|
|
72
|
+
// Fetch by exact AWS secret name
|
|
73
|
+
const secret = await getSecret("my-project/production/api-key");
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Note: `getSecret` requires `AWS_SESSION_TOKEN` and always calls Secrets Manager. Prefer `getEnvSecret` for typical use cases.
|
|
77
|
+
|
|
39
78
|
## Creating Secrets
|
|
40
79
|
|
|
41
80
|
### Via CDK
|
|
@@ -86,7 +125,7 @@ aws secretsmanager create-secret \
|
|
|
86
125
|
Retrieve in code:
|
|
87
126
|
|
|
88
127
|
```typescript
|
|
89
|
-
const credentialsJson = await
|
|
128
|
+
const credentialsJson = await getEnvSecret("DB_CREDENTIALS");
|
|
90
129
|
const credentials = JSON.parse(credentialsJson);
|
|
91
130
|
```
|
|
92
131
|
|
|
@@ -96,10 +135,10 @@ Secrets are cached by default to reduce API calls:
|
|
|
96
135
|
|
|
97
136
|
```typescript
|
|
98
137
|
// First call: fetches from Secrets Manager
|
|
99
|
-
const key1 = await
|
|
138
|
+
const key1 = await getEnvSecret("API_KEY");
|
|
100
139
|
|
|
101
140
|
// Second call: returns cached value
|
|
102
|
-
const key2 = await
|
|
141
|
+
const key2 = await getEnvSecret("API_KEY");
|
|
103
142
|
```
|
|
104
143
|
|
|
105
144
|
Cache is scoped to Lambda execution context (warm starts reuse cache).
|
|
@@ -125,19 +164,16 @@ secret.addRotationSchedule("Rotation", {
|
|
|
125
164
|
|
|
126
165
|
## Local Development
|
|
127
166
|
|
|
128
|
-
For local development,
|
|
167
|
+
For local development, set environment variables directly:
|
|
129
168
|
|
|
130
169
|
```bash
|
|
131
170
|
# .env.local (not committed)
|
|
171
|
+
ANTHROPIC_API_KEY=sk-ant-test123
|
|
132
172
|
MONGODB_URI=mongodb://localhost:27017/dev
|
|
133
173
|
API_KEY=test_key_123
|
|
134
174
|
```
|
|
135
175
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
const mongoUri = process.env.MONGODB_URI || await getSecret(process.env.SECRET_MONGODB_URI);
|
|
140
|
-
```
|
|
176
|
+
`getEnvSecret` automatically returns these values without AWS calls since there's no `SECRET_` prefix.
|
|
141
177
|
|
|
142
178
|
## IAM Permissions
|
|
143
179
|
|
|
@@ -153,3 +189,23 @@ lambdaFunction.addToRolePolicy(new PolicyStatement({
|
|
|
153
189
|
}));
|
|
154
190
|
```
|
|
155
191
|
|
|
192
|
+
## Testing
|
|
193
|
+
|
|
194
|
+
Mock secret functions in tests:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { getEnvSecret } from "@jaypie/testkit/mock";
|
|
198
|
+
import { vi } from "vitest";
|
|
199
|
+
|
|
200
|
+
vi.mock("@jaypie/aws");
|
|
201
|
+
|
|
202
|
+
describe("Handler", () => {
|
|
203
|
+
it("uses API key from secrets", async () => {
|
|
204
|
+
vi.mocked(getEnvSecret).mockResolvedValue("test-api-key");
|
|
205
|
+
|
|
206
|
+
const result = await handler();
|
|
207
|
+
|
|
208
|
+
expect(getEnvSecret).toHaveBeenCalledWith("API_KEY");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
```
|
package/skills/skills.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description:
|
|
3
|
+
related:
|
|
4
|
+
agents: Prompts to improve agent adoption in AGENTS.md, CLAUDE.md, etc.
|
|
5
|
+
index: All available skills
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Jaypie Skills
|
|
9
|
+
|
|
10
|
+
- Instruct Jaypie styles, techniques, and traditions
|
|
11
|
+
- Written for agents
|
|
12
|
+
- Balance brevity and rigor
|
|
13
|
+
|
|
14
|
+
Look up skills by their "alias:"
|
|
15
|
+
`mcp__jaypie__skills(index)`
|
|
16
|
+
|
|
17
|
+
## Essentials
|
|
18
|
+
|
|
19
|
+
Contents: index, releasenotes
|
|
20
|
+
Development: documentation, errors, logs, mocks, style, tests
|
|
21
|
+
Infrastructure: aws, cdk, cicd, datadog, dns, dynamodb, secrets, variables
|
|
22
|
+
Patterns: fabric, models, services, vocabulary
|
|
23
|
+
Meta: issues, jaypie, skills, tools
|