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 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)
@@ -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 };
@@ -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
+ }