lewnpc 0.1.0
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/LICENSE +21 -0
- package/README.md +241 -0
- package/dist/index.d.mts +204 -0
- package/dist/index.d.ts +204 -0
- package/dist/index.js +215 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +209 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Swarmgram Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# lewnpc
|
|
2
|
+
|
|
3
|
+
**NPCs that remember. Dialogue that levels up.**
|
|
4
|
+
|
|
5
|
+
Drop-in persistent memory and stable personality for game characters. One NPM package. One API call. Your NPC remembers every player, evolves every session, and never breaks character.
|
|
6
|
+
|
|
7
|
+
→ [lewnpc.com](https://lewnpc.com) · [Get API Key](https://lewnpc.com/#pricing) · [Docs](https://docs.lewnpc.com)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install lewnpc
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { LewNPC } from 'lewnpc';
|
|
21
|
+
|
|
22
|
+
const client = new LewNPC({
|
|
23
|
+
apiKey: process.env.LEWNPC_API_KEY,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Create an NPC with Big Five personality traits
|
|
27
|
+
const edric = await client.createNPC({
|
|
28
|
+
id: "edric_blacksmith",
|
|
29
|
+
role: "Village blacksmith and veteran of the Northern War",
|
|
30
|
+
backstory: "Lost his partner Maren to an avalanche on the northern pass. Quiet but warm once trust is earned.",
|
|
31
|
+
traits: {
|
|
32
|
+
openness: 0.3, // Traditional, practical
|
|
33
|
+
conscientiousness: 0.8, // Meticulous craftsman
|
|
34
|
+
extraversion: 0.5, // Opens up over time
|
|
35
|
+
agreeableness: 0.7, // Kind but not a pushover
|
|
36
|
+
neuroticism: 0.2, // Steady, rarely rattled
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Send a player message
|
|
41
|
+
const response = await client.message({
|
|
42
|
+
npc_id: "edric_blacksmith",
|
|
43
|
+
player_id: "player_7f2a",
|
|
44
|
+
message: "I need a sword for the northern pass.",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(response.text);
|
|
48
|
+
// "The northern pass? That route's been closed since the avalanche took
|
|
49
|
+
// Maren's caravan. I can forge you a short blade — but you'll want a
|
|
50
|
+
// cloak more than a sword up there."
|
|
51
|
+
|
|
52
|
+
console.log(response.emotion); // "concerned"
|
|
53
|
+
console.log(response.memory_delta); // ["Player intends to travel the northern pass", ...]
|
|
54
|
+
console.log(response.memory_count); // 2
|
|
55
|
+
console.log(response.latency_ms); // 174
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## The NPC remembers
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Next session — same npc_id + player_id, different day
|
|
62
|
+
const laterResponse = await client.message({
|
|
63
|
+
npc_id: "edric_blacksmith",
|
|
64
|
+
player_id: "player_7f2a",
|
|
65
|
+
message: "I made it back from the pass.",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
console.log(laterResponse.text);
|
|
69
|
+
// "Ha! Told you about the cloak, didn't I? Maren would've said the same —
|
|
70
|
+
// she knew those mountains better than anyone. Come, let me see that blade."
|
|
71
|
+
|
|
72
|
+
console.log(laterResponse.emotion); // "warm"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
No special setup. No session management. The NPC remembers because the API stores what it learned.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
### `new LewNPC(config)`
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const client = new LewNPC({
|
|
85
|
+
apiKey: string, // required — get one at lewnpc.com
|
|
86
|
+
baseUrl?: string, // default: https://api.lewnpc.com/v1
|
|
87
|
+
timeout?: number, // default: 30000ms
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### `client.createNPC(params)`
|
|
94
|
+
|
|
95
|
+
Create a character. Trait scores shape every response the character ever gives.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
await client.createNPC({
|
|
99
|
+
id: string, // unique ID — you choose it
|
|
100
|
+
role: string, // character role/title
|
|
101
|
+
backstory?: string, // optional backstory
|
|
102
|
+
traits: {
|
|
103
|
+
openness: number, // 0.0–1.0
|
|
104
|
+
conscientiousness: number,
|
|
105
|
+
extraversion: number,
|
|
106
|
+
agreeableness: number,
|
|
107
|
+
neuroticism: number,
|
|
108
|
+
},
|
|
109
|
+
initial_memories?: string[], // optional seed memories
|
|
110
|
+
metadata?: object, // arbitrary data, not used by model
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### `client.message(params)` → `MessageResponse`
|
|
117
|
+
|
|
118
|
+
Send a player message and get a response. This is the main method.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
await client.message({
|
|
122
|
+
npc_id: string, // which NPC
|
|
123
|
+
player_id: string, // which player — memory is per (npc, player) pair
|
|
124
|
+
message: string, // the player's message
|
|
125
|
+
context?: string, // optional per-turn context ("Player just defeated the boss")
|
|
126
|
+
temperature?: number, // default: 0.7
|
|
127
|
+
max_tokens?: number, // default: 512
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Response:**
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
{
|
|
135
|
+
text: string, // the NPC's dialogue
|
|
136
|
+
emotion: string, // current emotional state ("warm", "concerned", "nostalgic", ...)
|
|
137
|
+
memory_delta: string[], // what the NPC learned this turn
|
|
138
|
+
memory_count: number, // total memories held for this player
|
|
139
|
+
latency_ms: number, // actual inference time
|
|
140
|
+
model: string, // "lewis-2.0"
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### `client.listMemories(params)` → `ListMemoriesResponse`
|
|
147
|
+
|
|
148
|
+
Inspect everything an NPC remembers about a player.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const { memories, total } = await client.listMemories({
|
|
152
|
+
npc_id: "edric_blacksmith",
|
|
153
|
+
player_id: "player_7f2a",
|
|
154
|
+
limit: 50, // optional, default 50
|
|
155
|
+
offset: 0, // optional, for pagination
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
memories.forEach(m => console.log(`[Turn ${m.turn}] ${m.content}`));
|
|
159
|
+
// [Turn 1] Player intends to travel the northern pass
|
|
160
|
+
// [Turn 1] Player may not know about the avalanche
|
|
161
|
+
// [Turn 3] Player survived the northern pass
|
|
162
|
+
// [Turn 3] Relationship: strengthening — shared experience
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### `client.resetMemory(params)`
|
|
168
|
+
|
|
169
|
+
Reset a player's memory with an NPC. The NPC will treat their next message as a first meeting.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
await client.resetMemory({
|
|
173
|
+
npc_id: "edric_blacksmith",
|
|
174
|
+
player_id: "player_7f2a",
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### `client.getNPC(id)` / `client.listNPCs()` / `client.deleteNPC(id)`
|
|
181
|
+
|
|
182
|
+
Standard CRUD.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Error handling
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { LewNPC, LewNPCError, LewNPCRateLimitError, LewNPCAuthError } from 'lewnpc';
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const response = await client.message({ ... });
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (err instanceof LewNPCAuthError) {
|
|
195
|
+
console.error("Invalid API key — get one at lewnpc.com");
|
|
196
|
+
} else if (err instanceof LewNPCRateLimitError) {
|
|
197
|
+
console.error(`Rate limited. Retry after ${err.retryAfter}s`);
|
|
198
|
+
} else if (err instanceof LewNPCError) {
|
|
199
|
+
console.error(`LewNPC error ${err.status}: ${err.message} (${err.code})`);
|
|
200
|
+
} else {
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## The Big Five
|
|
209
|
+
|
|
210
|
+
LewNPC uses the [Big Five (OCEAN) model](https://en.wikipedia.org/wiki/Big_Five_personality_traits) for personality. All scores are floats between `0.0` and `1.0`.
|
|
211
|
+
|
|
212
|
+
| Trait | Low (0.0) | High (1.0) |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| **Openness** | Practical, conventional, prefers routine | Curious, creative, open to new ideas |
|
|
215
|
+
| **Conscientiousness** | Flexible, spontaneous, disorganized | Organized, reliable, goal-directed |
|
|
216
|
+
| **Extraversion** | Reserved, solitary, quiet | Outgoing, energetic, talkative |
|
|
217
|
+
| **Agreeableness** | Competitive, critical, blunt | Cooperative, warm, empathetic |
|
|
218
|
+
| **Neuroticism** | Calm, stable, resilient | Anxious, moody, easily stressed |
|
|
219
|
+
|
|
220
|
+
Traits are stable across the lifetime of a character. They don't drift. A blacksmith with `neuroticism: 0.2` stays steady whether it's their first interaction or their thousandth.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Powered by Lewis
|
|
225
|
+
|
|
226
|
+
LewNPC runs on [Lewis](https://lewis.works) — an 8B parameter language model fine-tuned on 1M+ behavioral interactions from [Swarmgram](https://swarmgram.com)'s 20,000-agent social simulation. Not a prompt wrapper on GPT-4. A purpose-built behavioral model.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Pricing
|
|
231
|
+
|
|
232
|
+
- **Free**: 3 NPCs, 1,000 messages/month
|
|
233
|
+
- **Pro**: $29/month — unlimited NPCs, 50K messages/month
|
|
234
|
+
|
|
235
|
+
[See full pricing →](https://lewnpc.com/#pricing)
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT — see [LICENSE](./LICENSE)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Big Five (OCEAN) personality trait scores.
|
|
3
|
+
* All values between 0.0 and 1.0.
|
|
4
|
+
*/
|
|
5
|
+
interface BigFiveTraits {
|
|
6
|
+
/** Openness to experience: curiosity, creativity, novelty-seeking (0–1) */
|
|
7
|
+
openness: number;
|
|
8
|
+
/** Conscientiousness: organization, reliability, self-discipline (0–1) */
|
|
9
|
+
conscientiousness: number;
|
|
10
|
+
/** Extraversion: sociability, assertiveness, positive affect (0–1) */
|
|
11
|
+
extraversion: number;
|
|
12
|
+
/** Agreeableness: warmth, cooperation, empathy (0–1) */
|
|
13
|
+
agreeableness: number;
|
|
14
|
+
/** Neuroticism: anxiety, moodiness, emotional instability (0–1) */
|
|
15
|
+
neuroticism: number;
|
|
16
|
+
}
|
|
17
|
+
interface CreateNPCParams {
|
|
18
|
+
/** Unique identifier for this NPC. Must be unique within your account. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** The character's role or title. Used as grounding context. */
|
|
21
|
+
role: string;
|
|
22
|
+
/** Optional backstory or character history. */
|
|
23
|
+
backstory?: string;
|
|
24
|
+
/** Big Five personality trait scores. */
|
|
25
|
+
traits: BigFiveTraits;
|
|
26
|
+
/**
|
|
27
|
+
* Optional seed memories to inject before any player interaction.
|
|
28
|
+
* Useful for characters with known history.
|
|
29
|
+
*/
|
|
30
|
+
initial_memories?: string[];
|
|
31
|
+
/** Optional metadata object stored alongside the NPC. Not used by the model. */
|
|
32
|
+
metadata?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
interface NPC {
|
|
35
|
+
id: string;
|
|
36
|
+
role: string;
|
|
37
|
+
backstory: string | null;
|
|
38
|
+
traits: BigFiveTraits;
|
|
39
|
+
memory_count: number;
|
|
40
|
+
created_at: string;
|
|
41
|
+
updated_at: string;
|
|
42
|
+
metadata: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
interface MessageParams {
|
|
45
|
+
/** ID of the NPC to message. */
|
|
46
|
+
npc_id: string;
|
|
47
|
+
/**
|
|
48
|
+
* Identifier for the player sending the message.
|
|
49
|
+
* The NPC maintains separate memory per player_id.
|
|
50
|
+
*/
|
|
51
|
+
player_id: string;
|
|
52
|
+
/** The player's message text. */
|
|
53
|
+
message: string;
|
|
54
|
+
/**
|
|
55
|
+
* Optional context to inject for this turn only.
|
|
56
|
+
* Useful for in-game events (e.g. "The player has just defeated the boss").
|
|
57
|
+
*/
|
|
58
|
+
context?: string;
|
|
59
|
+
/** Sampling temperature. Default: 0.7 */
|
|
60
|
+
temperature?: number;
|
|
61
|
+
/** Maximum tokens for the response. Default: 512 */
|
|
62
|
+
max_tokens?: number;
|
|
63
|
+
}
|
|
64
|
+
interface MessageResponse {
|
|
65
|
+
/** The NPC's dialogue response. */
|
|
66
|
+
text: string;
|
|
67
|
+
/**
|
|
68
|
+
* The NPC's current emotional state.
|
|
69
|
+
* Examples: "neutral", "warm", "concerned", "suspicious", "nostalgic", "amused"
|
|
70
|
+
*/
|
|
71
|
+
emotion: string;
|
|
72
|
+
/**
|
|
73
|
+
* Structured list of what the NPC learned or remembered from this interaction.
|
|
74
|
+
* Returned as an array of concise memory strings.
|
|
75
|
+
* Store these if you want to render them in-game.
|
|
76
|
+
*/
|
|
77
|
+
memory_delta: string[];
|
|
78
|
+
/** Total number of memories this NPC holds for this player_id. */
|
|
79
|
+
memory_count: number;
|
|
80
|
+
/** Actual inference latency in milliseconds. */
|
|
81
|
+
latency_ms: number;
|
|
82
|
+
/** Model version used for this response. */
|
|
83
|
+
model: string;
|
|
84
|
+
}
|
|
85
|
+
interface Memory {
|
|
86
|
+
id: string;
|
|
87
|
+
player_id: string;
|
|
88
|
+
content: string;
|
|
89
|
+
turn: number;
|
|
90
|
+
created_at: string;
|
|
91
|
+
}
|
|
92
|
+
interface ListMemoriesParams {
|
|
93
|
+
npc_id: string;
|
|
94
|
+
player_id: string;
|
|
95
|
+
limit?: number;
|
|
96
|
+
offset?: number;
|
|
97
|
+
}
|
|
98
|
+
interface ListMemoriesResponse {
|
|
99
|
+
memories: Memory[];
|
|
100
|
+
total: number;
|
|
101
|
+
player_id: string;
|
|
102
|
+
}
|
|
103
|
+
interface ResetMemoryParams {
|
|
104
|
+
npc_id: string;
|
|
105
|
+
player_id: string;
|
|
106
|
+
}
|
|
107
|
+
interface LewNPCConfig {
|
|
108
|
+
/** Your LewNPC API key. Get one at https://lewnpc.com */
|
|
109
|
+
apiKey: string;
|
|
110
|
+
/**
|
|
111
|
+
* API base URL. Defaults to https://api.lewnpc.com/v1
|
|
112
|
+
* Override for local development or self-hosted deployments.
|
|
113
|
+
*/
|
|
114
|
+
baseUrl?: string;
|
|
115
|
+
/** Request timeout in milliseconds. Default: 30000 */
|
|
116
|
+
timeout?: number;
|
|
117
|
+
}
|
|
118
|
+
declare class LewNPCError extends Error {
|
|
119
|
+
readonly status: number;
|
|
120
|
+
readonly code: string;
|
|
121
|
+
constructor(message: string, status: number, code: string);
|
|
122
|
+
}
|
|
123
|
+
declare class LewNPCAuthError extends LewNPCError {
|
|
124
|
+
constructor();
|
|
125
|
+
}
|
|
126
|
+
declare class LewNPCNotFoundError extends LewNPCError {
|
|
127
|
+
constructor(resource: string);
|
|
128
|
+
}
|
|
129
|
+
declare class LewNPCRateLimitError extends LewNPCError {
|
|
130
|
+
readonly retryAfter: number;
|
|
131
|
+
constructor(retryAfter?: number);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
declare class LewNPC {
|
|
135
|
+
private readonly apiKey;
|
|
136
|
+
private readonly baseUrl;
|
|
137
|
+
private readonly timeout;
|
|
138
|
+
constructor(config: LewNPCConfig);
|
|
139
|
+
private request;
|
|
140
|
+
/**
|
|
141
|
+
* Create a new NPC with a personality profile.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* const edric = await client.createNPC({
|
|
145
|
+
* id: "edric_blacksmith",
|
|
146
|
+
* role: "Village blacksmith and veteran of the Northern War",
|
|
147
|
+
* backstory: "Lost his partner Maren to an avalanche. Quiet but warm once trust is earned.",
|
|
148
|
+
* traits: {
|
|
149
|
+
* openness: 0.3,
|
|
150
|
+
* conscientiousness: 0.8,
|
|
151
|
+
* extraversion: 0.5,
|
|
152
|
+
* agreeableness: 0.7,
|
|
153
|
+
* neuroticism: 0.2,
|
|
154
|
+
* },
|
|
155
|
+
* });
|
|
156
|
+
*/
|
|
157
|
+
createNPC(params: CreateNPCParams): Promise<NPC>;
|
|
158
|
+
/**
|
|
159
|
+
* Retrieve an existing NPC by ID.
|
|
160
|
+
*/
|
|
161
|
+
getNPC(npcId: string): Promise<NPC>;
|
|
162
|
+
/**
|
|
163
|
+
* List all NPCs in your account.
|
|
164
|
+
*/
|
|
165
|
+
listNPCs(): Promise<NPC[]>;
|
|
166
|
+
/**
|
|
167
|
+
* Delete an NPC and all associated memories.
|
|
168
|
+
* This action is permanent.
|
|
169
|
+
*/
|
|
170
|
+
deleteNPC(npcId: string): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Send a message to an NPC and get a personality-consistent, memory-grounded response.
|
|
173
|
+
*
|
|
174
|
+
* The NPC remembers every interaction with each player_id across sessions.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* const response = await client.message({
|
|
178
|
+
* npc_id: "edric_blacksmith",
|
|
179
|
+
* player_id: "player_7f2a",
|
|
180
|
+
* message: "I need a sword for the northern pass.",
|
|
181
|
+
* });
|
|
182
|
+
*
|
|
183
|
+
* console.log(response.text);
|
|
184
|
+
* // "The northern pass? That route's been closed since the avalanche..."
|
|
185
|
+
*
|
|
186
|
+
* console.log(response.emotion);
|
|
187
|
+
* // "concerned"
|
|
188
|
+
*
|
|
189
|
+
* console.log(response.memory_delta);
|
|
190
|
+
* // ["Player intends to travel the northern pass", "Player may not know about the avalanche"]
|
|
191
|
+
*/
|
|
192
|
+
message(params: MessageParams): Promise<MessageResponse>;
|
|
193
|
+
/**
|
|
194
|
+
* List all memories an NPC holds for a specific player.
|
|
195
|
+
*/
|
|
196
|
+
listMemories(params: ListMemoriesParams): Promise<ListMemoriesResponse>;
|
|
197
|
+
/**
|
|
198
|
+
* Reset all memories an NPC holds for a specific player.
|
|
199
|
+
* The NPC will treat subsequent messages from this player as a first meeting.
|
|
200
|
+
*/
|
|
201
|
+
resetMemory(params: ResetMemoryParams): Promise<void>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { type BigFiveTraits, type CreateNPCParams, LewNPC, LewNPCAuthError, type LewNPCConfig, LewNPCError, LewNPCNotFoundError, LewNPCRateLimitError, type ListMemoriesParams, type ListMemoriesResponse, type Memory, type MessageParams, type MessageResponse, type NPC, type ResetMemoryParams };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Big Five (OCEAN) personality trait scores.
|
|
3
|
+
* All values between 0.0 and 1.0.
|
|
4
|
+
*/
|
|
5
|
+
interface BigFiveTraits {
|
|
6
|
+
/** Openness to experience: curiosity, creativity, novelty-seeking (0–1) */
|
|
7
|
+
openness: number;
|
|
8
|
+
/** Conscientiousness: organization, reliability, self-discipline (0–1) */
|
|
9
|
+
conscientiousness: number;
|
|
10
|
+
/** Extraversion: sociability, assertiveness, positive affect (0–1) */
|
|
11
|
+
extraversion: number;
|
|
12
|
+
/** Agreeableness: warmth, cooperation, empathy (0–1) */
|
|
13
|
+
agreeableness: number;
|
|
14
|
+
/** Neuroticism: anxiety, moodiness, emotional instability (0–1) */
|
|
15
|
+
neuroticism: number;
|
|
16
|
+
}
|
|
17
|
+
interface CreateNPCParams {
|
|
18
|
+
/** Unique identifier for this NPC. Must be unique within your account. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** The character's role or title. Used as grounding context. */
|
|
21
|
+
role: string;
|
|
22
|
+
/** Optional backstory or character history. */
|
|
23
|
+
backstory?: string;
|
|
24
|
+
/** Big Five personality trait scores. */
|
|
25
|
+
traits: BigFiveTraits;
|
|
26
|
+
/**
|
|
27
|
+
* Optional seed memories to inject before any player interaction.
|
|
28
|
+
* Useful for characters with known history.
|
|
29
|
+
*/
|
|
30
|
+
initial_memories?: string[];
|
|
31
|
+
/** Optional metadata object stored alongside the NPC. Not used by the model. */
|
|
32
|
+
metadata?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
interface NPC {
|
|
35
|
+
id: string;
|
|
36
|
+
role: string;
|
|
37
|
+
backstory: string | null;
|
|
38
|
+
traits: BigFiveTraits;
|
|
39
|
+
memory_count: number;
|
|
40
|
+
created_at: string;
|
|
41
|
+
updated_at: string;
|
|
42
|
+
metadata: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
interface MessageParams {
|
|
45
|
+
/** ID of the NPC to message. */
|
|
46
|
+
npc_id: string;
|
|
47
|
+
/**
|
|
48
|
+
* Identifier for the player sending the message.
|
|
49
|
+
* The NPC maintains separate memory per player_id.
|
|
50
|
+
*/
|
|
51
|
+
player_id: string;
|
|
52
|
+
/** The player's message text. */
|
|
53
|
+
message: string;
|
|
54
|
+
/**
|
|
55
|
+
* Optional context to inject for this turn only.
|
|
56
|
+
* Useful for in-game events (e.g. "The player has just defeated the boss").
|
|
57
|
+
*/
|
|
58
|
+
context?: string;
|
|
59
|
+
/** Sampling temperature. Default: 0.7 */
|
|
60
|
+
temperature?: number;
|
|
61
|
+
/** Maximum tokens for the response. Default: 512 */
|
|
62
|
+
max_tokens?: number;
|
|
63
|
+
}
|
|
64
|
+
interface MessageResponse {
|
|
65
|
+
/** The NPC's dialogue response. */
|
|
66
|
+
text: string;
|
|
67
|
+
/**
|
|
68
|
+
* The NPC's current emotional state.
|
|
69
|
+
* Examples: "neutral", "warm", "concerned", "suspicious", "nostalgic", "amused"
|
|
70
|
+
*/
|
|
71
|
+
emotion: string;
|
|
72
|
+
/**
|
|
73
|
+
* Structured list of what the NPC learned or remembered from this interaction.
|
|
74
|
+
* Returned as an array of concise memory strings.
|
|
75
|
+
* Store these if you want to render them in-game.
|
|
76
|
+
*/
|
|
77
|
+
memory_delta: string[];
|
|
78
|
+
/** Total number of memories this NPC holds for this player_id. */
|
|
79
|
+
memory_count: number;
|
|
80
|
+
/** Actual inference latency in milliseconds. */
|
|
81
|
+
latency_ms: number;
|
|
82
|
+
/** Model version used for this response. */
|
|
83
|
+
model: string;
|
|
84
|
+
}
|
|
85
|
+
interface Memory {
|
|
86
|
+
id: string;
|
|
87
|
+
player_id: string;
|
|
88
|
+
content: string;
|
|
89
|
+
turn: number;
|
|
90
|
+
created_at: string;
|
|
91
|
+
}
|
|
92
|
+
interface ListMemoriesParams {
|
|
93
|
+
npc_id: string;
|
|
94
|
+
player_id: string;
|
|
95
|
+
limit?: number;
|
|
96
|
+
offset?: number;
|
|
97
|
+
}
|
|
98
|
+
interface ListMemoriesResponse {
|
|
99
|
+
memories: Memory[];
|
|
100
|
+
total: number;
|
|
101
|
+
player_id: string;
|
|
102
|
+
}
|
|
103
|
+
interface ResetMemoryParams {
|
|
104
|
+
npc_id: string;
|
|
105
|
+
player_id: string;
|
|
106
|
+
}
|
|
107
|
+
interface LewNPCConfig {
|
|
108
|
+
/** Your LewNPC API key. Get one at https://lewnpc.com */
|
|
109
|
+
apiKey: string;
|
|
110
|
+
/**
|
|
111
|
+
* API base URL. Defaults to https://api.lewnpc.com/v1
|
|
112
|
+
* Override for local development or self-hosted deployments.
|
|
113
|
+
*/
|
|
114
|
+
baseUrl?: string;
|
|
115
|
+
/** Request timeout in milliseconds. Default: 30000 */
|
|
116
|
+
timeout?: number;
|
|
117
|
+
}
|
|
118
|
+
declare class LewNPCError extends Error {
|
|
119
|
+
readonly status: number;
|
|
120
|
+
readonly code: string;
|
|
121
|
+
constructor(message: string, status: number, code: string);
|
|
122
|
+
}
|
|
123
|
+
declare class LewNPCAuthError extends LewNPCError {
|
|
124
|
+
constructor();
|
|
125
|
+
}
|
|
126
|
+
declare class LewNPCNotFoundError extends LewNPCError {
|
|
127
|
+
constructor(resource: string);
|
|
128
|
+
}
|
|
129
|
+
declare class LewNPCRateLimitError extends LewNPCError {
|
|
130
|
+
readonly retryAfter: number;
|
|
131
|
+
constructor(retryAfter?: number);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
declare class LewNPC {
|
|
135
|
+
private readonly apiKey;
|
|
136
|
+
private readonly baseUrl;
|
|
137
|
+
private readonly timeout;
|
|
138
|
+
constructor(config: LewNPCConfig);
|
|
139
|
+
private request;
|
|
140
|
+
/**
|
|
141
|
+
* Create a new NPC with a personality profile.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* const edric = await client.createNPC({
|
|
145
|
+
* id: "edric_blacksmith",
|
|
146
|
+
* role: "Village blacksmith and veteran of the Northern War",
|
|
147
|
+
* backstory: "Lost his partner Maren to an avalanche. Quiet but warm once trust is earned.",
|
|
148
|
+
* traits: {
|
|
149
|
+
* openness: 0.3,
|
|
150
|
+
* conscientiousness: 0.8,
|
|
151
|
+
* extraversion: 0.5,
|
|
152
|
+
* agreeableness: 0.7,
|
|
153
|
+
* neuroticism: 0.2,
|
|
154
|
+
* },
|
|
155
|
+
* });
|
|
156
|
+
*/
|
|
157
|
+
createNPC(params: CreateNPCParams): Promise<NPC>;
|
|
158
|
+
/**
|
|
159
|
+
* Retrieve an existing NPC by ID.
|
|
160
|
+
*/
|
|
161
|
+
getNPC(npcId: string): Promise<NPC>;
|
|
162
|
+
/**
|
|
163
|
+
* List all NPCs in your account.
|
|
164
|
+
*/
|
|
165
|
+
listNPCs(): Promise<NPC[]>;
|
|
166
|
+
/**
|
|
167
|
+
* Delete an NPC and all associated memories.
|
|
168
|
+
* This action is permanent.
|
|
169
|
+
*/
|
|
170
|
+
deleteNPC(npcId: string): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Send a message to an NPC and get a personality-consistent, memory-grounded response.
|
|
173
|
+
*
|
|
174
|
+
* The NPC remembers every interaction with each player_id across sessions.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* const response = await client.message({
|
|
178
|
+
* npc_id: "edric_blacksmith",
|
|
179
|
+
* player_id: "player_7f2a",
|
|
180
|
+
* message: "I need a sword for the northern pass.",
|
|
181
|
+
* });
|
|
182
|
+
*
|
|
183
|
+
* console.log(response.text);
|
|
184
|
+
* // "The northern pass? That route's been closed since the avalanche..."
|
|
185
|
+
*
|
|
186
|
+
* console.log(response.emotion);
|
|
187
|
+
* // "concerned"
|
|
188
|
+
*
|
|
189
|
+
* console.log(response.memory_delta);
|
|
190
|
+
* // ["Player intends to travel the northern pass", "Player may not know about the avalanche"]
|
|
191
|
+
*/
|
|
192
|
+
message(params: MessageParams): Promise<MessageResponse>;
|
|
193
|
+
/**
|
|
194
|
+
* List all memories an NPC holds for a specific player.
|
|
195
|
+
*/
|
|
196
|
+
listMemories(params: ListMemoriesParams): Promise<ListMemoriesResponse>;
|
|
197
|
+
/**
|
|
198
|
+
* Reset all memories an NPC holds for a specific player.
|
|
199
|
+
* The NPC will treat subsequent messages from this player as a first meeting.
|
|
200
|
+
*/
|
|
201
|
+
resetMemory(params: ResetMemoryParams): Promise<void>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { type BigFiveTraits, type CreateNPCParams, LewNPC, LewNPCAuthError, type LewNPCConfig, LewNPCError, LewNPCNotFoundError, LewNPCRateLimitError, type ListMemoriesParams, type ListMemoriesResponse, type Memory, type MessageParams, type MessageResponse, type NPC, type ResetMemoryParams };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/types.ts
|
|
4
|
+
var LewNPCError = class extends Error {
|
|
5
|
+
constructor(message, status, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "LewNPCError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var LewNPCAuthError = class extends LewNPCError {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("Invalid API key. Get one at https://lewnpc.com", 401, "UNAUTHORIZED");
|
|
15
|
+
this.name = "LewNPCAuthError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var LewNPCNotFoundError = class extends LewNPCError {
|
|
19
|
+
constructor(resource) {
|
|
20
|
+
super(`${resource} not found.`, 404, "NOT_FOUND");
|
|
21
|
+
this.name = "LewNPCNotFoundError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var LewNPCRateLimitError = class extends LewNPCError {
|
|
25
|
+
constructor(retryAfter = 60) {
|
|
26
|
+
super(
|
|
27
|
+
`Rate limit exceeded. Retry after ${retryAfter} seconds. Upgrade at https://lewnpc.com/pricing`,
|
|
28
|
+
429,
|
|
29
|
+
"RATE_LIMITED"
|
|
30
|
+
);
|
|
31
|
+
this.name = "LewNPCRateLimitError";
|
|
32
|
+
this.retryAfter = retryAfter;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/client.ts
|
|
37
|
+
var DEFAULT_BASE_URL = "https://api.lewnpc.com/v1";
|
|
38
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
39
|
+
var SDK_VERSION = "0.1.0";
|
|
40
|
+
var LewNPC = class {
|
|
41
|
+
constructor(config) {
|
|
42
|
+
if (!config.apiKey) {
|
|
43
|
+
throw new LewNPCError(
|
|
44
|
+
"apiKey is required. Get one at https://lewnpc.com",
|
|
45
|
+
400,
|
|
46
|
+
"MISSING_API_KEY"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
this.apiKey = config.apiKey;
|
|
50
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
51
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
52
|
+
}
|
|
53
|
+
// ─── Internal fetch ───────────────────────────────────────────────────────
|
|
54
|
+
async request(method, path, body) {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
57
|
+
let response;
|
|
58
|
+
try {
|
|
59
|
+
response = await fetch(`${this.baseUrl}${path}`, {
|
|
60
|
+
method,
|
|
61
|
+
headers: {
|
|
62
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
"User-Agent": `lewnpc-node/${SDK_VERSION}`,
|
|
65
|
+
"X-SDK-Version": SDK_VERSION
|
|
66
|
+
},
|
|
67
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
68
|
+
signal: controller.signal
|
|
69
|
+
});
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (err.name === "AbortError") {
|
|
72
|
+
throw new LewNPCError(
|
|
73
|
+
`Request timed out after ${this.timeout}ms`,
|
|
74
|
+
408,
|
|
75
|
+
"TIMEOUT"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
throw new LewNPCError(
|
|
79
|
+
`Network error: ${err.message}`,
|
|
80
|
+
0,
|
|
81
|
+
"NETWORK_ERROR"
|
|
82
|
+
);
|
|
83
|
+
} finally {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
}
|
|
86
|
+
if (response.ok) {
|
|
87
|
+
if (response.status === 204) return void 0;
|
|
88
|
+
return response.json();
|
|
89
|
+
}
|
|
90
|
+
const status = response.status;
|
|
91
|
+
if (status === 401) throw new LewNPCAuthError();
|
|
92
|
+
if (status === 429) {
|
|
93
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "60", 10);
|
|
94
|
+
throw new LewNPCRateLimitError(retryAfter);
|
|
95
|
+
}
|
|
96
|
+
let errorBody = {};
|
|
97
|
+
try {
|
|
98
|
+
errorBody = await response.json();
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
if (status === 404) {
|
|
102
|
+
throw new LewNPCNotFoundError(errorBody.error ?? "Resource");
|
|
103
|
+
}
|
|
104
|
+
throw new LewNPCError(
|
|
105
|
+
errorBody.error ?? `Request failed with status ${status}`,
|
|
106
|
+
status,
|
|
107
|
+
errorBody.code ?? "API_ERROR"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
// ─── NPCs ─────────────────────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Create a new NPC with a personality profile.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* const edric = await client.createNPC({
|
|
116
|
+
* id: "edric_blacksmith",
|
|
117
|
+
* role: "Village blacksmith and veteran of the Northern War",
|
|
118
|
+
* backstory: "Lost his partner Maren to an avalanche. Quiet but warm once trust is earned.",
|
|
119
|
+
* traits: {
|
|
120
|
+
* openness: 0.3,
|
|
121
|
+
* conscientiousness: 0.8,
|
|
122
|
+
* extraversion: 0.5,
|
|
123
|
+
* agreeableness: 0.7,
|
|
124
|
+
* neuroticism: 0.2,
|
|
125
|
+
* },
|
|
126
|
+
* });
|
|
127
|
+
*/
|
|
128
|
+
async createNPC(params) {
|
|
129
|
+
return this.request("POST", "/npcs", params);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Retrieve an existing NPC by ID.
|
|
133
|
+
*/
|
|
134
|
+
async getNPC(npcId) {
|
|
135
|
+
return this.request("GET", `/npcs/${encodeURIComponent(npcId)}`);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* List all NPCs in your account.
|
|
139
|
+
*/
|
|
140
|
+
async listNPCs() {
|
|
141
|
+
return this.request("GET", "/npcs");
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Delete an NPC and all associated memories.
|
|
145
|
+
* This action is permanent.
|
|
146
|
+
*/
|
|
147
|
+
async deleteNPC(npcId) {
|
|
148
|
+
return this.request("DELETE", `/npcs/${encodeURIComponent(npcId)}`);
|
|
149
|
+
}
|
|
150
|
+
// ─── Messages ─────────────────────────────────────────────────────────────
|
|
151
|
+
/**
|
|
152
|
+
* Send a message to an NPC and get a personality-consistent, memory-grounded response.
|
|
153
|
+
*
|
|
154
|
+
* The NPC remembers every interaction with each player_id across sessions.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* const response = await client.message({
|
|
158
|
+
* npc_id: "edric_blacksmith",
|
|
159
|
+
* player_id: "player_7f2a",
|
|
160
|
+
* message: "I need a sword for the northern pass.",
|
|
161
|
+
* });
|
|
162
|
+
*
|
|
163
|
+
* console.log(response.text);
|
|
164
|
+
* // "The northern pass? That route's been closed since the avalanche..."
|
|
165
|
+
*
|
|
166
|
+
* console.log(response.emotion);
|
|
167
|
+
* // "concerned"
|
|
168
|
+
*
|
|
169
|
+
* console.log(response.memory_delta);
|
|
170
|
+
* // ["Player intends to travel the northern pass", "Player may not know about the avalanche"]
|
|
171
|
+
*/
|
|
172
|
+
async message(params) {
|
|
173
|
+
const { npc_id, ...body } = params;
|
|
174
|
+
return this.request(
|
|
175
|
+
"POST",
|
|
176
|
+
`/npcs/${encodeURIComponent(npc_id)}/message`,
|
|
177
|
+
body
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
// ─── Memory ───────────────────────────────────────────────────────────────
|
|
181
|
+
/**
|
|
182
|
+
* List all memories an NPC holds for a specific player.
|
|
183
|
+
*/
|
|
184
|
+
async listMemories(params) {
|
|
185
|
+
const { npc_id, player_id, limit = 50, offset = 0 } = params;
|
|
186
|
+
const qs = new URLSearchParams({
|
|
187
|
+
player_id,
|
|
188
|
+
limit: String(limit),
|
|
189
|
+
offset: String(offset)
|
|
190
|
+
});
|
|
191
|
+
return this.request(
|
|
192
|
+
"GET",
|
|
193
|
+
`/npcs/${encodeURIComponent(npc_id)}/memories?${qs}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Reset all memories an NPC holds for a specific player.
|
|
198
|
+
* The NPC will treat subsequent messages from this player as a first meeting.
|
|
199
|
+
*/
|
|
200
|
+
async resetMemory(params) {
|
|
201
|
+
const { npc_id, player_id } = params;
|
|
202
|
+
return this.request(
|
|
203
|
+
"DELETE",
|
|
204
|
+
`/npcs/${encodeURIComponent(npc_id)}/memories?player_id=${encodeURIComponent(player_id)}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
exports.LewNPC = LewNPC;
|
|
210
|
+
exports.LewNPCAuthError = LewNPCAuthError;
|
|
211
|
+
exports.LewNPCError = LewNPCError;
|
|
212
|
+
exports.LewNPCNotFoundError = LewNPCNotFoundError;
|
|
213
|
+
exports.LewNPCRateLimitError = LewNPCRateLimitError;
|
|
214
|
+
//# sourceMappingURL=index.js.map
|
|
215
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ─── Personality ─────────────────────────────────────────────────────────────\n\n/**\n * Big Five (OCEAN) personality trait scores.\n * All values between 0.0 and 1.0.\n */\nexport interface BigFiveTraits {\n /** Openness to experience: curiosity, creativity, novelty-seeking (0–1) */\n openness: number;\n /** Conscientiousness: organization, reliability, self-discipline (0–1) */\n conscientiousness: number;\n /** Extraversion: sociability, assertiveness, positive affect (0–1) */\n extraversion: number;\n /** Agreeableness: warmth, cooperation, empathy (0–1) */\n agreeableness: number;\n /** Neuroticism: anxiety, moodiness, emotional instability (0–1) */\n neuroticism: number;\n}\n\n// ─── NPC ─────────────────────────────────────────────────────────────────────\n\nexport interface CreateNPCParams {\n /** Unique identifier for this NPC. Must be unique within your account. */\n id: string;\n /** The character's role or title. Used as grounding context. */\n role: string;\n /** Optional backstory or character history. */\n backstory?: string;\n /** Big Five personality trait scores. */\n traits: BigFiveTraits;\n /**\n * Optional seed memories to inject before any player interaction.\n * Useful for characters with known history.\n */\n initial_memories?: string[];\n /** Optional metadata object stored alongside the NPC. Not used by the model. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface NPC {\n id: string;\n role: string;\n backstory: string | null;\n traits: BigFiveTraits;\n memory_count: number;\n created_at: string;\n updated_at: string;\n metadata: Record<string, unknown>;\n}\n\n// ─── Messages ────────────────────────────────────────────────────────────────\n\nexport interface MessageParams {\n /** ID of the NPC to message. */\n npc_id: string;\n /**\n * Identifier for the player sending the message.\n * The NPC maintains separate memory per player_id.\n */\n player_id: string;\n /** The player's message text. */\n message: string;\n /**\n * Optional context to inject for this turn only.\n * Useful for in-game events (e.g. \"The player has just defeated the boss\").\n */\n context?: string;\n /** Sampling temperature. Default: 0.7 */\n temperature?: number;\n /** Maximum tokens for the response. Default: 512 */\n max_tokens?: number;\n}\n\nexport interface MessageResponse {\n /** The NPC's dialogue response. */\n text: string;\n /**\n * The NPC's current emotional state.\n * Examples: \"neutral\", \"warm\", \"concerned\", \"suspicious\", \"nostalgic\", \"amused\"\n */\n emotion: string;\n /**\n * Structured list of what the NPC learned or remembered from this interaction.\n * Returned as an array of concise memory strings.\n * Store these if you want to render them in-game.\n */\n memory_delta: string[];\n /** Total number of memories this NPC holds for this player_id. */\n memory_count: number;\n /** Actual inference latency in milliseconds. */\n latency_ms: number;\n /** Model version used for this response. */\n model: string;\n}\n\n// ─── Memory ──────────────────────────────────────────────────────────────────\n\nexport interface Memory {\n id: string;\n player_id: string;\n content: string;\n turn: number;\n created_at: string;\n}\n\nexport interface ListMemoriesParams {\n npc_id: string;\n player_id: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface ListMemoriesResponse {\n memories: Memory[];\n total: number;\n player_id: string;\n}\n\nexport interface ResetMemoryParams {\n npc_id: string;\n player_id: string;\n}\n\n// ─── Client Config ───────────────────────────────────────────────────────────\n\nexport interface LewNPCConfig {\n /** Your LewNPC API key. Get one at https://lewnpc.com */\n apiKey: string;\n /**\n * API base URL. Defaults to https://api.lewnpc.com/v1\n * Override for local development or self-hosted deployments.\n */\n baseUrl?: string;\n /** Request timeout in milliseconds. Default: 30000 */\n timeout?: number;\n}\n\n// ─── Errors ──────────────────────────────────────────────────────────────────\n\nexport interface LewNPCErrorResponse {\n error: string;\n code: string;\n status: number;\n}\n\nexport class LewNPCError extends Error {\n readonly status: number;\n readonly code: string;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = \"LewNPCError\";\n this.status = status;\n this.code = code;\n }\n}\n\nexport class LewNPCAuthError extends LewNPCError {\n constructor() {\n super(\"Invalid API key. Get one at https://lewnpc.com\", 401, \"UNAUTHORIZED\");\n this.name = \"LewNPCAuthError\";\n }\n}\n\nexport class LewNPCNotFoundError extends LewNPCError {\n constructor(resource: string) {\n super(`${resource} not found.`, 404, \"NOT_FOUND\");\n this.name = \"LewNPCNotFoundError\";\n }\n}\n\nexport class LewNPCRateLimitError extends LewNPCError {\n readonly retryAfter: number;\n\n constructor(retryAfter = 60) {\n super(\n `Rate limit exceeded. Retry after ${retryAfter} seconds. Upgrade at https://lewnpc.com/pricing`,\n 429,\n \"RATE_LIMITED\"\n );\n this.name = \"LewNPCRateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n","import {\n LewNPCConfig,\n CreateNPCParams,\n NPC,\n MessageParams,\n MessageResponse,\n ListMemoriesParams,\n ListMemoriesResponse,\n ResetMemoryParams,\n LewNPCError,\n LewNPCAuthError,\n LewNPCNotFoundError,\n LewNPCRateLimitError,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.lewnpc.com/v1\";\nconst DEFAULT_TIMEOUT = 30_000;\nconst SDK_VERSION = \"0.1.0\";\n\nexport class LewNPC {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: LewNPCConfig) {\n if (!config.apiKey) {\n throw new LewNPCError(\n \"apiKey is required. Get one at https://lewnpc.com\",\n 400,\n \"MISSING_API_KEY\"\n );\n }\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n }\n\n // ─── Internal fetch ───────────────────────────────────────────────────────\n\n private async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n body?: unknown\n ): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n let response: Response;\n try {\n response = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": `lewnpc-node/${SDK_VERSION}`,\n \"X-SDK-Version\": SDK_VERSION,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new LewNPCError(\n `Request timed out after ${this.timeout}ms`,\n 408,\n \"TIMEOUT\"\n );\n }\n throw new LewNPCError(\n `Network error: ${(err as Error).message}`,\n 0,\n \"NETWORK_ERROR\"\n );\n } finally {\n clearTimeout(timer);\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return response.json() as Promise<T>;\n }\n\n // Error handling\n const status = response.status;\n\n if (status === 401) throw new LewNPCAuthError();\n if (status === 429) {\n const retryAfter = parseInt(response.headers.get(\"Retry-After\") ?? \"60\", 10);\n throw new LewNPCRateLimitError(retryAfter);\n }\n\n let errorBody: { error?: string; code?: string } = {};\n try { errorBody = await response.json() as { error?: string; code?: string }; } catch { /* ignore parse errors */ }\n\n if (status === 404) {\n throw new LewNPCNotFoundError(errorBody.error ?? \"Resource\");\n }\n\n throw new LewNPCError(\n errorBody.error ?? `Request failed with status ${status}`,\n status,\n errorBody.code ?? \"API_ERROR\"\n );\n }\n\n // ─── NPCs ─────────────────────────────────────────────────────────────────\n\n /**\n * Create a new NPC with a personality profile.\n *\n * @example\n * const edric = await client.createNPC({\n * id: \"edric_blacksmith\",\n * role: \"Village blacksmith and veteran of the Northern War\",\n * backstory: \"Lost his partner Maren to an avalanche. Quiet but warm once trust is earned.\",\n * traits: {\n * openness: 0.3,\n * conscientiousness: 0.8,\n * extraversion: 0.5,\n * agreeableness: 0.7,\n * neuroticism: 0.2,\n * },\n * });\n */\n async createNPC(params: CreateNPCParams): Promise<NPC> {\n return this.request<NPC>(\"POST\", \"/npcs\", params);\n }\n\n /**\n * Retrieve an existing NPC by ID.\n */\n async getNPC(npcId: string): Promise<NPC> {\n return this.request<NPC>(\"GET\", `/npcs/${encodeURIComponent(npcId)}`);\n }\n\n /**\n * List all NPCs in your account.\n */\n async listNPCs(): Promise<NPC[]> {\n return this.request<NPC[]>(\"GET\", \"/npcs\");\n }\n\n /**\n * Delete an NPC and all associated memories.\n * This action is permanent.\n */\n async deleteNPC(npcId: string): Promise<void> {\n return this.request<void>(\"DELETE\", `/npcs/${encodeURIComponent(npcId)}`);\n }\n\n // ─── Messages ─────────────────────────────────────────────────────────────\n\n /**\n * Send a message to an NPC and get a personality-consistent, memory-grounded response.\n *\n * The NPC remembers every interaction with each player_id across sessions.\n *\n * @example\n * const response = await client.message({\n * npc_id: \"edric_blacksmith\",\n * player_id: \"player_7f2a\",\n * message: \"I need a sword for the northern pass.\",\n * });\n *\n * console.log(response.text);\n * // \"The northern pass? That route's been closed since the avalanche...\"\n *\n * console.log(response.emotion);\n * // \"concerned\"\n *\n * console.log(response.memory_delta);\n * // [\"Player intends to travel the northern pass\", \"Player may not know about the avalanche\"]\n */\n async message(params: MessageParams): Promise<MessageResponse> {\n const { npc_id, ...body } = params;\n return this.request<MessageResponse>(\n \"POST\",\n `/npcs/${encodeURIComponent(npc_id)}/message`,\n body\n );\n }\n\n // ─── Memory ───────────────────────────────────────────────────────────────\n\n /**\n * List all memories an NPC holds for a specific player.\n */\n async listMemories(params: ListMemoriesParams): Promise<ListMemoriesResponse> {\n const { npc_id, player_id, limit = 50, offset = 0 } = params;\n const qs = new URLSearchParams({\n player_id,\n limit: String(limit),\n offset: String(offset),\n });\n return this.request<ListMemoriesResponse>(\n \"GET\",\n `/npcs/${encodeURIComponent(npc_id)}/memories?${qs}`\n );\n }\n\n /**\n * Reset all memories an NPC holds for a specific player.\n * The NPC will treat subsequent messages from this player as a first meeting.\n */\n async resetMemory(params: ResetMemoryParams): Promise<void> {\n const { npc_id, player_id } = params;\n return this.request<void>(\n \"DELETE\",\n `/npcs/${encodeURIComponent(npc_id)}/memories?player_id=${encodeURIComponent(player_id)}`\n );\n }\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var LewNPCError = class extends Error {
|
|
3
|
+
constructor(message, status, code) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "LewNPCError";
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var LewNPCAuthError = class extends LewNPCError {
|
|
11
|
+
constructor() {
|
|
12
|
+
super("Invalid API key. Get one at https://lewnpc.com", 401, "UNAUTHORIZED");
|
|
13
|
+
this.name = "LewNPCAuthError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var LewNPCNotFoundError = class extends LewNPCError {
|
|
17
|
+
constructor(resource) {
|
|
18
|
+
super(`${resource} not found.`, 404, "NOT_FOUND");
|
|
19
|
+
this.name = "LewNPCNotFoundError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var LewNPCRateLimitError = class extends LewNPCError {
|
|
23
|
+
constructor(retryAfter = 60) {
|
|
24
|
+
super(
|
|
25
|
+
`Rate limit exceeded. Retry after ${retryAfter} seconds. Upgrade at https://lewnpc.com/pricing`,
|
|
26
|
+
429,
|
|
27
|
+
"RATE_LIMITED"
|
|
28
|
+
);
|
|
29
|
+
this.name = "LewNPCRateLimitError";
|
|
30
|
+
this.retryAfter = retryAfter;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// src/client.ts
|
|
35
|
+
var DEFAULT_BASE_URL = "https://api.lewnpc.com/v1";
|
|
36
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
37
|
+
var SDK_VERSION = "0.1.0";
|
|
38
|
+
var LewNPC = class {
|
|
39
|
+
constructor(config) {
|
|
40
|
+
if (!config.apiKey) {
|
|
41
|
+
throw new LewNPCError(
|
|
42
|
+
"apiKey is required. Get one at https://lewnpc.com",
|
|
43
|
+
400,
|
|
44
|
+
"MISSING_API_KEY"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
this.apiKey = config.apiKey;
|
|
48
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
49
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
50
|
+
}
|
|
51
|
+
// ─── Internal fetch ───────────────────────────────────────────────────────
|
|
52
|
+
async request(method, path, body) {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
55
|
+
let response;
|
|
56
|
+
try {
|
|
57
|
+
response = await fetch(`${this.baseUrl}${path}`, {
|
|
58
|
+
method,
|
|
59
|
+
headers: {
|
|
60
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"User-Agent": `lewnpc-node/${SDK_VERSION}`,
|
|
63
|
+
"X-SDK-Version": SDK_VERSION
|
|
64
|
+
},
|
|
65
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
66
|
+
signal: controller.signal
|
|
67
|
+
});
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (err.name === "AbortError") {
|
|
70
|
+
throw new LewNPCError(
|
|
71
|
+
`Request timed out after ${this.timeout}ms`,
|
|
72
|
+
408,
|
|
73
|
+
"TIMEOUT"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
throw new LewNPCError(
|
|
77
|
+
`Network error: ${err.message}`,
|
|
78
|
+
0,
|
|
79
|
+
"NETWORK_ERROR"
|
|
80
|
+
);
|
|
81
|
+
} finally {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
}
|
|
84
|
+
if (response.ok) {
|
|
85
|
+
if (response.status === 204) return void 0;
|
|
86
|
+
return response.json();
|
|
87
|
+
}
|
|
88
|
+
const status = response.status;
|
|
89
|
+
if (status === 401) throw new LewNPCAuthError();
|
|
90
|
+
if (status === 429) {
|
|
91
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "60", 10);
|
|
92
|
+
throw new LewNPCRateLimitError(retryAfter);
|
|
93
|
+
}
|
|
94
|
+
let errorBody = {};
|
|
95
|
+
try {
|
|
96
|
+
errorBody = await response.json();
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
if (status === 404) {
|
|
100
|
+
throw new LewNPCNotFoundError(errorBody.error ?? "Resource");
|
|
101
|
+
}
|
|
102
|
+
throw new LewNPCError(
|
|
103
|
+
errorBody.error ?? `Request failed with status ${status}`,
|
|
104
|
+
status,
|
|
105
|
+
errorBody.code ?? "API_ERROR"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
// ─── NPCs ─────────────────────────────────────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Create a new NPC with a personality profile.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* const edric = await client.createNPC({
|
|
114
|
+
* id: "edric_blacksmith",
|
|
115
|
+
* role: "Village blacksmith and veteran of the Northern War",
|
|
116
|
+
* backstory: "Lost his partner Maren to an avalanche. Quiet but warm once trust is earned.",
|
|
117
|
+
* traits: {
|
|
118
|
+
* openness: 0.3,
|
|
119
|
+
* conscientiousness: 0.8,
|
|
120
|
+
* extraversion: 0.5,
|
|
121
|
+
* agreeableness: 0.7,
|
|
122
|
+
* neuroticism: 0.2,
|
|
123
|
+
* },
|
|
124
|
+
* });
|
|
125
|
+
*/
|
|
126
|
+
async createNPC(params) {
|
|
127
|
+
return this.request("POST", "/npcs", params);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Retrieve an existing NPC by ID.
|
|
131
|
+
*/
|
|
132
|
+
async getNPC(npcId) {
|
|
133
|
+
return this.request("GET", `/npcs/${encodeURIComponent(npcId)}`);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* List all NPCs in your account.
|
|
137
|
+
*/
|
|
138
|
+
async listNPCs() {
|
|
139
|
+
return this.request("GET", "/npcs");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Delete an NPC and all associated memories.
|
|
143
|
+
* This action is permanent.
|
|
144
|
+
*/
|
|
145
|
+
async deleteNPC(npcId) {
|
|
146
|
+
return this.request("DELETE", `/npcs/${encodeURIComponent(npcId)}`);
|
|
147
|
+
}
|
|
148
|
+
// ─── Messages ─────────────────────────────────────────────────────────────
|
|
149
|
+
/**
|
|
150
|
+
* Send a message to an NPC and get a personality-consistent, memory-grounded response.
|
|
151
|
+
*
|
|
152
|
+
* The NPC remembers every interaction with each player_id across sessions.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* const response = await client.message({
|
|
156
|
+
* npc_id: "edric_blacksmith",
|
|
157
|
+
* player_id: "player_7f2a",
|
|
158
|
+
* message: "I need a sword for the northern pass.",
|
|
159
|
+
* });
|
|
160
|
+
*
|
|
161
|
+
* console.log(response.text);
|
|
162
|
+
* // "The northern pass? That route's been closed since the avalanche..."
|
|
163
|
+
*
|
|
164
|
+
* console.log(response.emotion);
|
|
165
|
+
* // "concerned"
|
|
166
|
+
*
|
|
167
|
+
* console.log(response.memory_delta);
|
|
168
|
+
* // ["Player intends to travel the northern pass", "Player may not know about the avalanche"]
|
|
169
|
+
*/
|
|
170
|
+
async message(params) {
|
|
171
|
+
const { npc_id, ...body } = params;
|
|
172
|
+
return this.request(
|
|
173
|
+
"POST",
|
|
174
|
+
`/npcs/${encodeURIComponent(npc_id)}/message`,
|
|
175
|
+
body
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
// ─── Memory ───────────────────────────────────────────────────────────────
|
|
179
|
+
/**
|
|
180
|
+
* List all memories an NPC holds for a specific player.
|
|
181
|
+
*/
|
|
182
|
+
async listMemories(params) {
|
|
183
|
+
const { npc_id, player_id, limit = 50, offset = 0 } = params;
|
|
184
|
+
const qs = new URLSearchParams({
|
|
185
|
+
player_id,
|
|
186
|
+
limit: String(limit),
|
|
187
|
+
offset: String(offset)
|
|
188
|
+
});
|
|
189
|
+
return this.request(
|
|
190
|
+
"GET",
|
|
191
|
+
`/npcs/${encodeURIComponent(npc_id)}/memories?${qs}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Reset all memories an NPC holds for a specific player.
|
|
196
|
+
* The NPC will treat subsequent messages from this player as a first meeting.
|
|
197
|
+
*/
|
|
198
|
+
async resetMemory(params) {
|
|
199
|
+
const { npc_id, player_id } = params;
|
|
200
|
+
return this.request(
|
|
201
|
+
"DELETE",
|
|
202
|
+
`/npcs/${encodeURIComponent(npc_id)}/memories?player_id=${encodeURIComponent(player_id)}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export { LewNPC, LewNPCAuthError, LewNPCError, LewNPCNotFoundError, LewNPCRateLimitError };
|
|
208
|
+
//# sourceMappingURL=index.mjs.map
|
|
209
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// ─── Personality ─────────────────────────────────────────────────────────────\n\n/**\n * Big Five (OCEAN) personality trait scores.\n * All values between 0.0 and 1.0.\n */\nexport interface BigFiveTraits {\n /** Openness to experience: curiosity, creativity, novelty-seeking (0–1) */\n openness: number;\n /** Conscientiousness: organization, reliability, self-discipline (0–1) */\n conscientiousness: number;\n /** Extraversion: sociability, assertiveness, positive affect (0–1) */\n extraversion: number;\n /** Agreeableness: warmth, cooperation, empathy (0–1) */\n agreeableness: number;\n /** Neuroticism: anxiety, moodiness, emotional instability (0–1) */\n neuroticism: number;\n}\n\n// ─── NPC ─────────────────────────────────────────────────────────────────────\n\nexport interface CreateNPCParams {\n /** Unique identifier for this NPC. Must be unique within your account. */\n id: string;\n /** The character's role or title. Used as grounding context. */\n role: string;\n /** Optional backstory or character history. */\n backstory?: string;\n /** Big Five personality trait scores. */\n traits: BigFiveTraits;\n /**\n * Optional seed memories to inject before any player interaction.\n * Useful for characters with known history.\n */\n initial_memories?: string[];\n /** Optional metadata object stored alongside the NPC. Not used by the model. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface NPC {\n id: string;\n role: string;\n backstory: string | null;\n traits: BigFiveTraits;\n memory_count: number;\n created_at: string;\n updated_at: string;\n metadata: Record<string, unknown>;\n}\n\n// ─── Messages ────────────────────────────────────────────────────────────────\n\nexport interface MessageParams {\n /** ID of the NPC to message. */\n npc_id: string;\n /**\n * Identifier for the player sending the message.\n * The NPC maintains separate memory per player_id.\n */\n player_id: string;\n /** The player's message text. */\n message: string;\n /**\n * Optional context to inject for this turn only.\n * Useful for in-game events (e.g. \"The player has just defeated the boss\").\n */\n context?: string;\n /** Sampling temperature. Default: 0.7 */\n temperature?: number;\n /** Maximum tokens for the response. Default: 512 */\n max_tokens?: number;\n}\n\nexport interface MessageResponse {\n /** The NPC's dialogue response. */\n text: string;\n /**\n * The NPC's current emotional state.\n * Examples: \"neutral\", \"warm\", \"concerned\", \"suspicious\", \"nostalgic\", \"amused\"\n */\n emotion: string;\n /**\n * Structured list of what the NPC learned or remembered from this interaction.\n * Returned as an array of concise memory strings.\n * Store these if you want to render them in-game.\n */\n memory_delta: string[];\n /** Total number of memories this NPC holds for this player_id. */\n memory_count: number;\n /** Actual inference latency in milliseconds. */\n latency_ms: number;\n /** Model version used for this response. */\n model: string;\n}\n\n// ─── Memory ──────────────────────────────────────────────────────────────────\n\nexport interface Memory {\n id: string;\n player_id: string;\n content: string;\n turn: number;\n created_at: string;\n}\n\nexport interface ListMemoriesParams {\n npc_id: string;\n player_id: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface ListMemoriesResponse {\n memories: Memory[];\n total: number;\n player_id: string;\n}\n\nexport interface ResetMemoryParams {\n npc_id: string;\n player_id: string;\n}\n\n// ─── Client Config ───────────────────────────────────────────────────────────\n\nexport interface LewNPCConfig {\n /** Your LewNPC API key. Get one at https://lewnpc.com */\n apiKey: string;\n /**\n * API base URL. Defaults to https://api.lewnpc.com/v1\n * Override for local development or self-hosted deployments.\n */\n baseUrl?: string;\n /** Request timeout in milliseconds. Default: 30000 */\n timeout?: number;\n}\n\n// ─── Errors ──────────────────────────────────────────────────────────────────\n\nexport interface LewNPCErrorResponse {\n error: string;\n code: string;\n status: number;\n}\n\nexport class LewNPCError extends Error {\n readonly status: number;\n readonly code: string;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = \"LewNPCError\";\n this.status = status;\n this.code = code;\n }\n}\n\nexport class LewNPCAuthError extends LewNPCError {\n constructor() {\n super(\"Invalid API key. Get one at https://lewnpc.com\", 401, \"UNAUTHORIZED\");\n this.name = \"LewNPCAuthError\";\n }\n}\n\nexport class LewNPCNotFoundError extends LewNPCError {\n constructor(resource: string) {\n super(`${resource} not found.`, 404, \"NOT_FOUND\");\n this.name = \"LewNPCNotFoundError\";\n }\n}\n\nexport class LewNPCRateLimitError extends LewNPCError {\n readonly retryAfter: number;\n\n constructor(retryAfter = 60) {\n super(\n `Rate limit exceeded. Retry after ${retryAfter} seconds. Upgrade at https://lewnpc.com/pricing`,\n 429,\n \"RATE_LIMITED\"\n );\n this.name = \"LewNPCRateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n","import {\n LewNPCConfig,\n CreateNPCParams,\n NPC,\n MessageParams,\n MessageResponse,\n ListMemoriesParams,\n ListMemoriesResponse,\n ResetMemoryParams,\n LewNPCError,\n LewNPCAuthError,\n LewNPCNotFoundError,\n LewNPCRateLimitError,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.lewnpc.com/v1\";\nconst DEFAULT_TIMEOUT = 30_000;\nconst SDK_VERSION = \"0.1.0\";\n\nexport class LewNPC {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: LewNPCConfig) {\n if (!config.apiKey) {\n throw new LewNPCError(\n \"apiKey is required. Get one at https://lewnpc.com\",\n 400,\n \"MISSING_API_KEY\"\n );\n }\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n }\n\n // ─── Internal fetch ───────────────────────────────────────────────────────\n\n private async request<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n body?: unknown\n ): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n let response: Response;\n try {\n response = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": `lewnpc-node/${SDK_VERSION}`,\n \"X-SDK-Version\": SDK_VERSION,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new LewNPCError(\n `Request timed out after ${this.timeout}ms`,\n 408,\n \"TIMEOUT\"\n );\n }\n throw new LewNPCError(\n `Network error: ${(err as Error).message}`,\n 0,\n \"NETWORK_ERROR\"\n );\n } finally {\n clearTimeout(timer);\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return response.json() as Promise<T>;\n }\n\n // Error handling\n const status = response.status;\n\n if (status === 401) throw new LewNPCAuthError();\n if (status === 429) {\n const retryAfter = parseInt(response.headers.get(\"Retry-After\") ?? \"60\", 10);\n throw new LewNPCRateLimitError(retryAfter);\n }\n\n let errorBody: { error?: string; code?: string } = {};\n try { errorBody = await response.json() as { error?: string; code?: string }; } catch { /* ignore parse errors */ }\n\n if (status === 404) {\n throw new LewNPCNotFoundError(errorBody.error ?? \"Resource\");\n }\n\n throw new LewNPCError(\n errorBody.error ?? `Request failed with status ${status}`,\n status,\n errorBody.code ?? \"API_ERROR\"\n );\n }\n\n // ─── NPCs ─────────────────────────────────────────────────────────────────\n\n /**\n * Create a new NPC with a personality profile.\n *\n * @example\n * const edric = await client.createNPC({\n * id: \"edric_blacksmith\",\n * role: \"Village blacksmith and veteran of the Northern War\",\n * backstory: \"Lost his partner Maren to an avalanche. Quiet but warm once trust is earned.\",\n * traits: {\n * openness: 0.3,\n * conscientiousness: 0.8,\n * extraversion: 0.5,\n * agreeableness: 0.7,\n * neuroticism: 0.2,\n * },\n * });\n */\n async createNPC(params: CreateNPCParams): Promise<NPC> {\n return this.request<NPC>(\"POST\", \"/npcs\", params);\n }\n\n /**\n * Retrieve an existing NPC by ID.\n */\n async getNPC(npcId: string): Promise<NPC> {\n return this.request<NPC>(\"GET\", `/npcs/${encodeURIComponent(npcId)}`);\n }\n\n /**\n * List all NPCs in your account.\n */\n async listNPCs(): Promise<NPC[]> {\n return this.request<NPC[]>(\"GET\", \"/npcs\");\n }\n\n /**\n * Delete an NPC and all associated memories.\n * This action is permanent.\n */\n async deleteNPC(npcId: string): Promise<void> {\n return this.request<void>(\"DELETE\", `/npcs/${encodeURIComponent(npcId)}`);\n }\n\n // ─── Messages ─────────────────────────────────────────────────────────────\n\n /**\n * Send a message to an NPC and get a personality-consistent, memory-grounded response.\n *\n * The NPC remembers every interaction with each player_id across sessions.\n *\n * @example\n * const response = await client.message({\n * npc_id: \"edric_blacksmith\",\n * player_id: \"player_7f2a\",\n * message: \"I need a sword for the northern pass.\",\n * });\n *\n * console.log(response.text);\n * // \"The northern pass? That route's been closed since the avalanche...\"\n *\n * console.log(response.emotion);\n * // \"concerned\"\n *\n * console.log(response.memory_delta);\n * // [\"Player intends to travel the northern pass\", \"Player may not know about the avalanche\"]\n */\n async message(params: MessageParams): Promise<MessageResponse> {\n const { npc_id, ...body } = params;\n return this.request<MessageResponse>(\n \"POST\",\n `/npcs/${encodeURIComponent(npc_id)}/message`,\n body\n );\n }\n\n // ─── Memory ───────────────────────────────────────────────────────────────\n\n /**\n * List all memories an NPC holds for a specific player.\n */\n async listMemories(params: ListMemoriesParams): Promise<ListMemoriesResponse> {\n const { npc_id, player_id, limit = 50, offset = 0 } = params;\n const qs = new URLSearchParams({\n player_id,\n limit: String(limit),\n offset: String(offset),\n });\n return this.request<ListMemoriesResponse>(\n \"GET\",\n `/npcs/${encodeURIComponent(npc_id)}/memories?${qs}`\n );\n }\n\n /**\n * Reset all memories an NPC holds for a specific player.\n * The NPC will treat subsequent messages from this player as a first meeting.\n */\n async resetMemory(params: ResetMemoryParams): Promise<void> {\n const { npc_id, player_id } = params;\n return this.request<void>(\n \"DELETE\",\n `/npcs/${encodeURIComponent(npc_id)}/memories?player_id=${encodeURIComponent(player_id)}`\n );\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lewnpc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistent memory and stable personality for game NPCs. One API. Lewis underneath.",
|
|
5
|
+
"keywords": ["npc", "game-development", "ai", "persistent-memory", "personality", "dialogue", "rpg"],
|
|
6
|
+
"homepage": "https://lewnpc.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/swarmgram/lewnpc"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Swarmgram <hello@lewnpc.com>",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"require": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"typecheck": "tsc --noEmit"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|