@sprucelabs/sprucebot-llm 18.0.1 → 18.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/README.md CHANGED
@@ -73,7 +73,7 @@ Here are the contents of that file for you to review now, rather than needing to
73
73
  import { stdin as input, stdout as output } from 'node:process'
74
74
  import * as readline from 'node:readline/promises'
75
75
  import dotenv from 'dotenv'
76
- import OpenAiAdapter from './bots/adapters/OpenAi'
76
+ import OpenAiAdapter from './bots/adapters/OpenAiAdapter'
77
77
  import SprucebotLlmFactory from './bots/SprucebotLlmFactory'
78
78
  import buildCallbackSkill from './examples/buildCallbackSkill'
79
79
  import buildFileTransformerSkill from './examples/buildFileTransformerSkill'
@@ -131,7 +131,7 @@ void (async () => {
131
131
  There are two different limits to be aware of:
132
132
 
133
133
  - `SprucebotLlmBotImpl.messageMemoryLimit` (default: `10`) controls how many messages are kept in the in-memory history on the Bot. Once the limit is hit, old messages are dropped and can no longer be sent to any adapter.
134
- - `OPENAI_MESSAGE_MEMORY_LIMIT` or `OpenAiAdapter.setMessageMemoryLimit(limit)` controls how many of those tracked messages are included when sending a request to OpenAI. `0` (the default) means "no additional limit" beyond the Bot history.
134
+ - `OPENAI_MESSAGE_MEMORY_LIMIT` or `adapter.setMemoryLimit(limit)` controls how many of those tracked messages are included when sending a request to OpenAI. `0` (the default) means "no additional limit" beyond the Bot history.
135
135
 
136
136
  To change the Bot history limit:
137
137
 
@@ -148,6 +148,28 @@ Additional OpenAI context controls:
148
148
 
149
149
  ## Adapters
150
150
 
151
+ ### Custom request headers
152
+
153
+ You can attach custom HTTP headers to every underlying provider request a bot makes — useful for gateways, request tracing, or provider beta feature flags. Headers are threaded through the Bot's normal `sendMessage(...)` flow, so you set them once on the Bot.
154
+
155
+ Set them when constructing the bot:
156
+
157
+ ```ts
158
+ const bot = bots.Bot({
159
+ youAre: 'a helpful assistant',
160
+ skill: mySkill,
161
+ headers: { 'x-my-trace-id': '1234' },
162
+ })
163
+ ```
164
+
165
+ Or update them at any time (e.g. to rotate a trace id per conversation):
166
+
167
+ ```ts
168
+ bot.setHeaders({ 'x-my-trace-id': '5678' })
169
+ ```
170
+
171
+ The headers flow through to the underlying OpenAI / Anthropic request on the next `sendMessage`. Adapters also accept them per-call via `SendMessageOptions` if you call `adapter.sendMessage(bot, { model, headers })` directly.
172
+
151
173
  ### OpenAI adapter configuration
152
174
 
153
175
  Required environment variable:
@@ -178,7 +200,7 @@ const adapter = OpenAiAdapter.Adapter(process.env.OPEN_AI_API_KEY!, {
178
200
 
179
201
  // Or set after creation
180
202
  adapter.setModel('gpt-4o')
181
- adapter.setMessageMemoryLimit(10)
203
+ adapter.setMemoryLimit(10)
182
204
  adapter.setReasoningEffort('low')
183
205
  ```
184
206
 
@@ -193,7 +215,7 @@ adapter.setReasoningEffort('low')
193
215
  - `reasoningEffort` - for reasoning models (`'low'`, `'medium'`, `'high'`)
194
216
  - `baseUrl` - custom API endpoint
195
217
  - `adapter.setModel(model)`: set a default model for all requests unless a Skill overrides it.
196
- - `adapter.setMessageMemoryLimit(limit)`: limit how many tracked messages are sent to OpenAI.
218
+ - `adapter.setMemoryLimit(limit)`: limit how many tracked messages are sent to OpenAI.
197
219
  - `adapter.setReasoningEffort(effort)`: set `reasoning_effort` for models that support it.
198
220
  - `adapter.getTokenUsage()`: returns cumulative [token usage](#tracking-token-usage) for this adapter. The OpenAI adapter currently returns zeros (not yet implemented).
199
221
  - `OpenAiAdapter.OpenAI`: assign a custom OpenAI client class (useful for tests).
@@ -230,7 +252,7 @@ const bots = SprucebotLlmFactory.Factory(adapter)
230
252
 
231
253
  #### Anthropic prompt caching
232
254
 
233
- The Anthropic adapter automatically enables [prompt caching](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching) by inserting an `ephemeral` cache breakpoint after the system prompt. This allows Anthropic to cache the static portion of the prompt (your `youAre` + skill instructions) and only re-process the changing chat history on each turn — reducing latency and cost on long conversations.
255
+ The Anthropic adapter automatically enables [prompt caching](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching) by inserting an `ephemeral` cache breakpoint (with a `1h` TTL) after the system prompt. This allows Anthropic to cache the static portion of the prompt (your `youAre` + skill instructions) and only re-process the changing chat history on each turn — reducing latency and cost on long conversations.
234
256
 
235
257
  Token usage (including cache creation and cache read tokens) is logged at the `info` level on each request:
236
258
 
@@ -689,6 +711,7 @@ skill.unserialize(skillSnapshot)
689
711
  | `clearMessageHistory()` | Drop all tracked messages |
690
712
  | `updateState(partialState)` | Update state and emit `did-update-state` |
691
713
  | `setSkill(skill)` | Swap the active skill |
714
+ | `setHeaders(headers)` | Set custom HTTP headers attached to subsequent provider requests |
692
715
  | `serialize()` | Snapshot of bot's current state, skill, and history |
693
716
  | `unserialize(serialized)` | Restore state from a previous `serialize()` snapshot |
694
717
 
@@ -698,6 +721,7 @@ skill.unserialize(skillSnapshot)
698
721
  |--------|-------------|
699
722
  | `updateState(partialState)` | Update skill state |
700
723
  | `getState()` | Get current state |
724
+ | `updateJobDescription(prompt)` | Change the skill's job description (`yourJobIfYouChooseToAcceptItIs`) |
701
725
  | `setModel(model)` | Change the model this skill uses |
702
726
  | `serialize()` | Snapshot of skill configuration and state |
703
727
  | `unserialize(serialized)` | Restore skill state from a previous `serialize()` snapshot |
@@ -11,6 +11,7 @@ export default class SprucebotLlmBotImpl<StateSchema extends Schema = Schema, St
11
11
  protected messages: LlmMessage[];
12
12
  protected skill?: SprucebotLLmSkill;
13
13
  private activeTurn?;
14
+ private headers?;
14
15
  constructor(options: BotOptions<StateSchema, State>);
15
16
  clearMessageHistory(): void;
16
17
  markAsDone(): void;
@@ -25,4 +26,5 @@ export default class SprucebotLlmBotImpl<StateSchema extends Schema = Schema, St
25
26
  getStateSchema(): StateSchema | undefined;
26
27
  getState(): Partial<State> | undefined;
27
28
  silentlySetState(state: Partial<State>): void;
29
+ setHeaders(headers: Record<string, string>): void;
28
30
  }
@@ -10,7 +10,7 @@ const InternalStateUpdater_1 = __importDefault(require("./InternalStateUpdater")
10
10
  const TurnRequest_1 = __importDefault(require("./TurnRequest"));
11
11
  class SprucebotLlmBotImpl extends mercury_event_emitter_1.AbstractEventEmitter {
12
12
  constructor(options) {
13
- const { adapter, youAre, stateSchema, state, skill } = options;
13
+ const { adapter, youAre, stateSchema, state, skill, headers } = options;
14
14
  super(llm_types_1.llmEventContract);
15
15
  this.isDone = false;
16
16
  this.messages = [];
@@ -18,6 +18,7 @@ class SprucebotLlmBotImpl extends mercury_event_emitter_1.AbstractEventEmitter {
18
18
  this.youAre = youAre;
19
19
  this.stateSchema = stateSchema;
20
20
  this.skill = skill;
21
+ this.headers = headers;
21
22
  this.state = stateSchema
22
23
  ? {
23
24
  ...(0, schema_1.defaultSchemaValues)(stateSchema),
@@ -72,6 +73,7 @@ class SprucebotLlmBotImpl extends mercury_event_emitter_1.AbstractEventEmitter {
72
73
  setDone: (isDone) => (this.isDone = isDone),
73
74
  skill: this.skill,
74
75
  trackMessage: this.trackMessage.bind(this),
76
+ headers: this.headers,
75
77
  });
76
78
  return this.activeTurn.sendMessage(llmMessage, cb);
77
79
  }
@@ -105,6 +107,9 @@ class SprucebotLlmBotImpl extends mercury_event_emitter_1.AbstractEventEmitter {
105
107
  silentlySetState(state) {
106
108
  this.state = state;
107
109
  }
110
+ setHeaders(headers) {
111
+ this.headers = headers;
112
+ }
108
113
  }
109
114
  SprucebotLlmBotImpl.messageMemoryLimit = 10;
110
115
  exports.default = SprucebotLlmBotImpl;
@@ -7,6 +7,7 @@ export default class TurnRequest {
7
7
  private optionallyUpdateState;
8
8
  private bot;
9
9
  private isCancelled;
10
+ private headers?;
10
11
  constructor(options: {
11
12
  trackMessage: (message: LlmMessage) => void;
12
13
  setDone: (isDone: boolean) => void;
@@ -14,6 +15,7 @@ export default class TurnRequest {
14
15
  skill?: SprucebotLLmSkill;
15
16
  adapter: LlmAdapter;
16
17
  optionallyUpdateState: (state?: Record<string, any>) => Promise<void>;
18
+ headers?: Record<string, string>;
17
19
  });
18
20
  cancel(): void;
19
21
  sendMessage(llmMessage: LlmMessage, cb?: MessageResponseCallback): Promise<string | null>;
@@ -8,13 +8,14 @@ const ResponseParserFactory_1 = __importDefault(require("../parsingResponses/Res
8
8
  class TurnRequest {
9
9
  constructor(options) {
10
10
  this.isCancelled = false;
11
- const { trackMessage, skill, bot, adapter, setDone, optionallyUpdateState, } = options;
11
+ const { trackMessage, skill, bot, adapter, setDone, optionallyUpdateState, headers, } = options;
12
12
  this.trackMessage = trackMessage;
13
13
  this.skill = skill;
14
14
  this.adapter = adapter;
15
15
  this.setDone = setDone;
16
16
  this.bot = bot;
17
17
  this.optionallyUpdateState = optionallyUpdateState;
18
+ this.headers = headers;
18
19
  }
19
20
  cancel() {
20
21
  this.isCancelled = true;
@@ -85,11 +86,18 @@ class TurnRequest {
85
86
  return parsedMessage;
86
87
  }
87
88
  async sendMessageToAdapter(model) {
88
- return await this.adapter.sendMessage(this.bot, model
89
- ? {
89
+ let options = undefined;
90
+ if (model) {
91
+ options = {
90
92
  model,
91
- }
92
- : undefined);
93
+ };
94
+ }
95
+ if (this.headers) {
96
+ options = options
97
+ ? { ...options, headers: this.headers }
98
+ : { headers: this.headers };
99
+ }
100
+ return await this.adapter.sendMessage(this.bot, options);
93
101
  }
94
102
  async parseResponse(response, callbacks) {
95
103
  const parser = ResponseParserFactory_1.default.getInstance();
@@ -35,4 +35,5 @@ export type MessageSenderSendMessageOptions = SendMessageOptions & {
35
35
  memoryLimit?: number;
36
36
  reasoningEffort?: ReasoningEffort;
37
37
  model: string;
38
+ headers?: Record<string, string>;
38
39
  };
@@ -45,16 +45,20 @@ class MessageSenderImpl {
45
45
  }
46
46
  }
47
47
  async send(options) {
48
- const { abortController, reasoningEffort, ...restOptions } = options;
48
+ const { abortController, reasoningEffort, headers, ...restOptions } = options;
49
49
  const params = {
50
50
  ...restOptions,
51
51
  };
52
52
  if (reasoningEffort) {
53
53
  params.reasoning_effort = reasoningEffort;
54
54
  }
55
- const response = await this.sendHandler(params, {
55
+ const request = {
56
56
  signal: abortController.signal,
57
- });
57
+ };
58
+ if (headers) {
59
+ request.headers = headers;
60
+ }
61
+ const response = await this.sendHandler(params, request);
58
62
  return response;
59
63
  }
60
64
  }
@@ -11,6 +11,7 @@ export default class SprucebotLlmBotImpl<StateSchema extends Schema = Schema, St
11
11
  protected messages: LlmMessage[];
12
12
  protected skill?: SprucebotLLmSkill;
13
13
  private activeTurn?;
14
+ private headers?;
14
15
  constructor(options: BotOptions<StateSchema, State>);
15
16
  clearMessageHistory(): void;
16
17
  markAsDone(): void;
@@ -25,4 +26,5 @@ export default class SprucebotLlmBotImpl<StateSchema extends Schema = Schema, St
25
26
  getStateSchema(): StateSchema | undefined;
26
27
  getState(): Partial<State> | undefined;
27
28
  silentlySetState(state: Partial<State>): void;
29
+ setHeaders(headers: Record<string, string>): void;
28
30
  }
@@ -14,7 +14,7 @@ import InternalStateUpdater from './InternalStateUpdater.js';
14
14
  import TurnRequest from './TurnRequest.js';
15
15
  class SprucebotLlmBotImpl extends AbstractEventEmitter {
16
16
  constructor(options) {
17
- const { adapter, youAre, stateSchema, state, skill } = options;
17
+ const { adapter, youAre, stateSchema, state, skill, headers } = options;
18
18
  super(llmEventContract);
19
19
  this.isDone = false;
20
20
  this.messages = [];
@@ -22,6 +22,7 @@ class SprucebotLlmBotImpl extends AbstractEventEmitter {
22
22
  this.youAre = youAre;
23
23
  this.stateSchema = stateSchema;
24
24
  this.skill = skill;
25
+ this.headers = headers;
25
26
  this.state = stateSchema
26
27
  ? Object.assign(Object.assign({}, defaultSchemaValues(stateSchema)), state)
27
28
  : undefined;
@@ -77,6 +78,7 @@ class SprucebotLlmBotImpl extends AbstractEventEmitter {
77
78
  setDone: (isDone) => (this.isDone = isDone),
78
79
  skill: this.skill,
79
80
  trackMessage: this.trackMessage.bind(this),
81
+ headers: this.headers,
80
82
  });
81
83
  return this.activeTurn.sendMessage(llmMessage, cb);
82
84
  });
@@ -116,6 +118,9 @@ class SprucebotLlmBotImpl extends AbstractEventEmitter {
116
118
  silentlySetState(state) {
117
119
  this.state = state;
118
120
  }
121
+ setHeaders(headers) {
122
+ this.headers = headers;
123
+ }
119
124
  }
120
125
  SprucebotLlmBotImpl.messageMemoryLimit = 10;
121
126
  export default SprucebotLlmBotImpl;
@@ -7,6 +7,7 @@ export default class TurnRequest {
7
7
  private optionallyUpdateState;
8
8
  private bot;
9
9
  private isCancelled;
10
+ private headers?;
10
11
  constructor(options: {
11
12
  trackMessage: (message: LlmMessage) => void;
12
13
  setDone: (isDone: boolean) => void;
@@ -14,6 +15,7 @@ export default class TurnRequest {
14
15
  skill?: SprucebotLLmSkill;
15
16
  adapter: LlmAdapter;
16
17
  optionallyUpdateState: (state?: Record<string, any>) => Promise<void>;
18
+ headers?: Record<string, string>;
17
19
  });
18
20
  cancel(): void;
19
21
  sendMessage(llmMessage: LlmMessage, cb?: MessageResponseCallback): Promise<string | null>;
@@ -12,13 +12,14 @@ import ResponseParserFactory from '../parsingResponses/ResponseParserFactory.js'
12
12
  export default class TurnRequest {
13
13
  constructor(options) {
14
14
  this.isCancelled = false;
15
- const { trackMessage, skill, bot, adapter, setDone, optionallyUpdateState, } = options;
15
+ const { trackMessage, skill, bot, adapter, setDone, optionallyUpdateState, headers, } = options;
16
16
  this.trackMessage = trackMessage;
17
17
  this.skill = skill;
18
18
  this.adapter = adapter;
19
19
  this.setDone = setDone;
20
20
  this.bot = bot;
21
21
  this.optionallyUpdateState = optionallyUpdateState;
22
+ this.headers = headers;
22
23
  }
23
24
  cancel() {
24
25
  this.isCancelled = true;
@@ -93,11 +94,17 @@ export default class TurnRequest {
93
94
  }
94
95
  sendMessageToAdapter(model) {
95
96
  return __awaiter(this, void 0, void 0, function* () {
96
- return yield this.adapter.sendMessage(this.bot, model
97
- ? {
97
+ let options = undefined;
98
+ if (model) {
99
+ options = {
98
100
  model,
99
- }
100
- : undefined);
101
+ };
102
+ }
103
+ if (this.headers) {
104
+ options = options
105
+ ? Object.assign(Object.assign({}, options), { headers: this.headers }) : { headers: this.headers };
106
+ }
107
+ return yield this.adapter.sendMessage(this.bot, options);
101
108
  });
102
109
  }
103
110
  parseResponse(response, callbacks) {
@@ -35,4 +35,5 @@ export type MessageSenderSendMessageOptions = SendMessageOptions & {
35
35
  memoryLimit?: number;
36
36
  reasoningEffort?: ReasoningEffort;
37
37
  model: string;
38
+ headers?: Record<string, string>;
38
39
  };
@@ -61,14 +61,18 @@ class MessageSenderImpl {
61
61
  }
62
62
  send(options) {
63
63
  return __awaiter(this, void 0, void 0, function* () {
64
- const { abortController, reasoningEffort } = options, restOptions = __rest(options, ["abortController", "reasoningEffort"]);
64
+ const { abortController, reasoningEffort, headers } = options, restOptions = __rest(options, ["abortController", "reasoningEffort", "headers"]);
65
65
  const params = Object.assign({}, restOptions);
66
66
  if (reasoningEffort) {
67
67
  params.reasoning_effort = reasoningEffort;
68
68
  }
69
- const response = yield this.sendHandler(params, {
69
+ const request = {
70
70
  signal: abortController.signal,
71
- });
71
+ };
72
+ if (headers) {
73
+ request.headers = headers;
74
+ }
75
+ const response = yield this.sendHandler(params, request);
72
76
  return response;
73
77
  });
74
78
  }
@@ -5,6 +5,7 @@ export interface BotOptions<StateSchema extends Schema = Schema, State extends S
5
5
  adapter: LlmAdapter;
6
6
  Class?: new (...opts: any[]) => SprucebotLlmBot<Schema, State>;
7
7
  skill?: SprucebotLLmSkill<Schema>;
8
+ headers?: Record<string, string>;
8
9
  }
9
10
  export interface SprucebotLlmBot<StateSchema extends Schema = Schema, State extends SchemaValues<StateSchema> = SchemaValues<StateSchema>> extends MercuryEventEmitter<LlmEventContract> {
10
11
  markAsDone(): void;
@@ -14,6 +15,7 @@ export interface SprucebotLlmBot<StateSchema extends Schema = Schema, State exte
14
15
  unserialize(serialized: SerializedBot<StateSchema, State>): void;
15
16
  updateState(state: Partial<State>): Promise<void>;
16
17
  setSkill(skill: SprucebotLLmSkill<any>): void;
18
+ setHeaders(headers: Record<string, string>): void;
17
19
  clearMessageHistory(): void;
18
20
  }
19
21
  export interface LlmAdapter {
@@ -41,6 +43,7 @@ export interface PromptOptions<StateSchema extends Schema, State extends SchemaV
41
43
  }
42
44
  export interface SendMessageOptions {
43
45
  model?: string;
46
+ headers?: Record<string, string>;
44
47
  }
45
48
  export interface SerializedBot<StateSchema extends Schema = Schema, State extends SchemaValues<StateSchema> = SchemaValues<StateSchema>> extends PromptOptions<Schema, State> {
46
49
  messages: LlmMessage[];
@@ -5,6 +5,7 @@ export interface BotOptions<StateSchema extends Schema = Schema, State extends S
5
5
  adapter: LlmAdapter;
6
6
  Class?: new (...opts: any[]) => SprucebotLlmBot<Schema, State>;
7
7
  skill?: SprucebotLLmSkill<Schema>;
8
+ headers?: Record<string, string>;
8
9
  }
9
10
  export interface SprucebotLlmBot<StateSchema extends Schema = Schema, State extends SchemaValues<StateSchema> = SchemaValues<StateSchema>> extends MercuryEventEmitter<LlmEventContract> {
10
11
  markAsDone(): void;
@@ -14,6 +15,7 @@ export interface SprucebotLlmBot<StateSchema extends Schema = Schema, State exte
14
15
  unserialize(serialized: SerializedBot<StateSchema, State>): void;
15
16
  updateState(state: Partial<State>): Promise<void>;
16
17
  setSkill(skill: SprucebotLLmSkill<any>): void;
18
+ setHeaders(headers: Record<string, string>): void;
17
19
  clearMessageHistory(): void;
18
20
  }
19
21
  export interface LlmAdapter {
@@ -41,6 +43,7 @@ export interface PromptOptions<StateSchema extends Schema, State extends SchemaV
41
43
  }
42
44
  export interface SendMessageOptions {
43
45
  model?: string;
46
+ headers?: Record<string, string>;
44
47
  }
45
48
  export interface SerializedBot<StateSchema extends Schema = Schema, State extends SchemaValues<StateSchema> = SchemaValues<StateSchema>> extends PromptOptions<Schema, State> {
46
49
  messages: LlmMessage[];
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "eta"
9
9
  ]
10
10
  },
11
- "version": "18.0.1",
11
+ "version": "18.1.0",
12
12
  "files": [
13
13
  "build"
14
14
  ],