@butlerbot/sdk 0.0.45 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -81,3 +81,4 @@ export type { OutreachRequestOptions, ListDeliveriesOptions, AnswerDeliveryOptio
81
81
  export { LinkConversationTransport } from "./modules/transport_link";
82
82
  export { SSEConversationTransport } from "./modules/transport_sse";
83
83
  export type { SteerResult, TurnStopMode, TurnStopped } from "./modules/transport";
84
+ export { RESERVED_TURN_FIELDS } from "./modules/transport";
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.SSEConversationTransport = exports.LinkConversationTransport = exports.answerDelivery = exports.listDeliveries = exports.updateJobSettings = exports.updateJob = exports.resumeJob = exports.cancelJob = exports.getJob = exports.listJobs = exports.ButlerBotAPIError = exports.Conversation = exports.ButlerBotClient = void 0;
17
+ exports.RESERVED_TURN_FIELDS = exports.SSEConversationTransport = exports.LinkConversationTransport = exports.answerDelivery = exports.listDeliveries = exports.updateJobSettings = exports.updateJob = exports.resumeJob = exports.cancelJob = exports.getJob = exports.listJobs = exports.ButlerBotAPIError = exports.Conversation = exports.ButlerBotClient = void 0;
18
18
  const config_1 = require("./config");
19
19
  const link_1 = require("./link");
20
20
  const conversation_1 = require("./modules/conversation");
@@ -137,3 +137,5 @@ var transport_link_1 = require("./modules/transport_link");
137
137
  Object.defineProperty(exports, "LinkConversationTransport", { enumerable: true, get: function () { return transport_link_1.LinkConversationTransport; } });
138
138
  var transport_sse_1 = require("./modules/transport_sse");
139
139
  Object.defineProperty(exports, "SSEConversationTransport", { enumerable: true, get: function () { return transport_sse_1.SSEConversationTransport; } });
140
+ var transport_1 = require("./modules/transport");
141
+ Object.defineProperty(exports, "RESERVED_TURN_FIELDS", { enumerable: true, get: function () { return transport_1.RESERVED_TURN_FIELDS; } });
@@ -122,6 +122,16 @@ export type LinkClientPayloads = {
122
122
  personality?: string;
123
123
  /** Why Alfred is speaking on this one turn: a line for its system prompt, at most 500 characters. */
124
124
  wake?: string;
125
+ /**
126
+ * What the platform attaches beside this turn's message: a `MessageContextItem[]`,
127
+ * JSON-encoded, passed to the server as it is. Absent when there are no items.
128
+ */
129
+ context?: string;
130
+ /**
131
+ * Further turn parameters, which the Link service spreads into the server's query.
132
+ * Never one of the fields above or the credential; absent when there are none.
133
+ */
134
+ extra?: Record<string, string>;
125
135
  };
126
136
  "conversation.end": {
127
137
  sessionId: string;
@@ -6,6 +6,7 @@ import { RequestResponseV3, RequestResponseV4 } from "../types/type_registry";
6
6
  import { RequestResponseV5 } from "../types/response/v5/dialogue_response_v5";
7
7
  import { ConversationStateResponse } from "../types/state/convo_state_response";
8
8
  import { ConversationAddress } from "../types/conversation/address";
9
+ import { MessageContextItem } from "../types/conversation/context";
9
10
  import { TurnProgressEntry } from "../types/response/v4/turn_registry_v4";
10
11
  import { TurnProgressEntryV5 } from "../types/response/v5/turn_registry_v5";
11
12
  export type DialogueRequestParams = {
@@ -35,6 +36,28 @@ export type DialogueRequestParams = {
35
36
  * 500 characters; the server refuses a longer one. Left off when blank.
36
37
  */
37
38
  wake?: string;
39
+ /**
40
+ * What the platform attaches beside this turn's message, never inside it: what it replies
41
+ * to, who it mentions, where it was sent. See `MessageContextItem` for the shape and the
42
+ * bounds (8 items, an 80-character title, a 4 000-character text, 12 000 characters in
43
+ * all), which the server enforces by refusing the turn. Passed per `send()`/`ask()`, since
44
+ * it belongs to the one message; left off when empty.
45
+ */
46
+ context?: MessageContextItem[];
47
+ /**
48
+ * Further parameters for this one turn, forwarded to the server verbatim: each entry
49
+ * becomes a query parameter over SSE, and a field of `extra` on the `conversation.chat`
50
+ * frame over a Link, which the Link service spreads into the same query. The SDK does not
51
+ * read them; the server validates whatever arrives, as it does its named parameters.
52
+ *
53
+ * This is how a new turn parameter reaches the server without an SDK change: the server
54
+ * learns to read it and the client sends it here.
55
+ *
56
+ * A key the transport already sends (`RESERVED_TURN_FIELDS`: `message`, `chatId`,
57
+ * `api_key`, `model`, `instructions`, `platform`, `address`, `personality`, `wake`,
58
+ * `context`) is refused: `send()` throws, and `ask()` rejects, before anything is sent.
59
+ */
60
+ extra?: Record<string, string>;
38
61
  };
39
62
  export type DialogueRequestOptions = Omit<Omit<DialogueRequestParams, "message">, "chatId">;
40
63
  export interface RequestResponseByVersion {
@@ -7,6 +7,7 @@
7
7
  * caller sees, including the payload shape, is identical either way.
8
8
  */
9
9
  import type { ConversationAddress } from "../types/conversation/address";
10
+ import type { MessageContextItem } from "../types/conversation/context";
10
11
  /** A turn in progress. */
11
12
  export type ConversationStream = {
12
13
  /**
@@ -60,7 +61,26 @@ export type TransportTurnRequest = {
60
61
  personality?: string;
61
62
  /** Why Alfred is speaking on this one turn: a line for its system prompt, at most 500 characters. */
62
63
  wake?: string;
64
+ /** What the platform attaches beside this turn's message. Sent only when non-empty. */
65
+ context?: MessageContextItem[];
66
+ /** Further turn parameters, forwarded to the server verbatim. See `DialogueRequestParams.extra`. */
67
+ extra?: Record<string, string>;
63
68
  };
69
+ /**
70
+ * The turn parameters the server reads by name, which a transport sends itself. An `extra`
71
+ * entry may not use one: it would replace what the transport sent, or, for `api_key`, the
72
+ * credential. Both transports refuse the same list, since over a Link the service spreads
73
+ * `extra` into the very query the SSE transport builds.
74
+ */
75
+ export declare const RESERVED_TURN_FIELDS: readonly string[];
76
+ /**
77
+ * A turn's `extra`, checked, or nothing when there is none to send. Throws on a key that
78
+ * collides with a field the transport sends, and on a value that is not a string, before
79
+ * anything reaches the wire.
80
+ */
81
+ export declare function turnExtra(extra: Record<string, string> | undefined): Record<string, string> | undefined;
82
+ /** A turn's context as it travels, JSON-encoded, or nothing when there is none. */
83
+ export declare function turnContext(context: MessageContextItem[] | undefined): string | undefined;
64
84
  export type TransportHandlers = {
65
85
  /** One payload of the stream, already in the shape callers expect. */
66
86
  payload(payload: unknown): void;
@@ -8,10 +8,58 @@
8
8
  * caller sees, including the payload shape, is identical either way.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.RESERVED_TURN_FIELDS = void 0;
12
+ exports.turnExtra = turnExtra;
13
+ exports.turnContext = turnContext;
11
14
  exports.noticePayload = noticePayload;
12
15
  exports.convoStartedPayload = convoStartedPayload;
13
16
  exports.completedPayload = completedPayload;
14
17
  exports.failurePayload = failurePayload;
18
+ /**
19
+ * The turn parameters the server reads by name, which a transport sends itself. An `extra`
20
+ * entry may not use one: it would replace what the transport sent, or, for `api_key`, the
21
+ * credential. Both transports refuse the same list, since over a Link the service spreads
22
+ * `extra` into the very query the SSE transport builds.
23
+ */
24
+ exports.RESERVED_TURN_FIELDS = [
25
+ "api_key",
26
+ "message",
27
+ "chatId",
28
+ "model",
29
+ "instructions",
30
+ "platform",
31
+ "address",
32
+ "personality",
33
+ "wake",
34
+ "context",
35
+ ];
36
+ /**
37
+ * A turn's `extra`, checked, or nothing when there is none to send. Throws on a key that
38
+ * collides with a field the transport sends, and on a value that is not a string, before
39
+ * anything reaches the wire.
40
+ */
41
+ function turnExtra(extra) {
42
+ if (!extra)
43
+ return undefined;
44
+ const entries = Object.entries(extra);
45
+ if (entries.length === 0)
46
+ return undefined;
47
+ for (const [key, value] of entries) {
48
+ if (!key)
49
+ throw new Error("A turn's `extra` may not have an empty key.");
50
+ if (exports.RESERVED_TURN_FIELDS.includes(key)) {
51
+ throw new Error(`A turn's \`extra\` may not set "${key}": the transport already sends it. Reserved: ${exports.RESERVED_TURN_FIELDS.join(", ")}.`);
52
+ }
53
+ if (typeof value !== "string") {
54
+ throw new Error(`A turn's \`extra\` values must be strings; "${key}" is ${typeof value}.`);
55
+ }
56
+ }
57
+ return Object.fromEntries(entries);
58
+ }
59
+ /** A turn's context as it travels, JSON-encoded, or nothing when there is none. */
60
+ function turnContext(context) {
61
+ return context && context.length > 0 ? JSON.stringify(context) : undefined;
62
+ }
15
63
  // =============================================
16
64
  // PAYLOAD SHAPES
17
65
  // =============================================
@@ -29,6 +29,8 @@ class LinkConversationTransport {
29
29
  link.on("disconnect", () => { this.sessionId = undefined; });
30
30
  }
31
31
  send(request, handlers) {
32
+ // Refused here, synchronously, as the SSE transport refuses it: before anything is sent.
33
+ (0, transport_1.turnExtra)(request.extra);
32
34
  const listening = { closed: false };
33
35
  const deliver = {
34
36
  payload: (payload) => { if (!listening.closed)
@@ -176,6 +178,8 @@ class LinkConversationTransport {
176
178
  }
177
179
  };
178
180
  learnChatId(progress.chatId);
181
+ const context = (0, transport_1.turnContext)(request.context);
182
+ const extra = (0, transport_1.turnExtra)(request.extra);
179
183
  let done;
180
184
  try {
181
185
  done = await this.link.exchange("conversation.chat", {
@@ -186,6 +190,8 @@ class LinkConversationTransport {
186
190
  ...(request.personality ? { personality: request.personality } : {}),
187
191
  // Per turn, never on the session: it says why Alfred was woken for this one.
188
192
  ...(request.wake?.trim() ? { wake: request.wake.trim() } : {}),
193
+ ...(context ? { context } : {}),
194
+ ...(extra ? { extra } : {}),
189
195
  }, {
190
196
  // A turn takes as long as it takes; only the transport dying ends it early.
191
197
  timeoutMs: 0,
@@ -4,6 +4,7 @@ exports.SSEConversationTransport = void 0;
4
4
  exports.streamSSE = streamSSE;
5
5
  const eventsource_1 = require("eventsource");
6
6
  const url_formatter_1 = require("../util/url_formatter");
7
+ const transport_1 = require("./transport");
7
8
  /**
8
9
  * Streams a server-sent-events endpoint, closing when the server says the stream is
9
10
  * done. Shared by turns and by the progress stream, which is SSE-only.
@@ -123,5 +124,10 @@ function asQuery(request) {
123
124
  const wake = request.wake?.trim();
124
125
  if (wake)
125
126
  query.wake = wake;
126
- return query;
127
+ const context = (0, transport_1.turnContext)(request.context);
128
+ if (context)
129
+ query.context = context;
130
+ // Checked before anything else is added, so an entry can never replace a field above.
131
+ const extra = (0, transport_1.turnExtra)(request.extra);
132
+ return extra ? { ...extra, ...query } : query;
127
133
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Something a platform attaches beside a user's message, never inside it: what the message
3
+ * replies to, who it mentions, where it was sent, the conversation around it.
4
+ *
5
+ * The text the user typed stays exactly what they typed. The model reads the items as a
6
+ * labelled block after it; a person sees them as chips under the message, titled by `title`
7
+ * and expandable to `text`.
8
+ *
9
+ * ```ts
10
+ * { kind: "reply", title: "Replying to", text: "Sam: what time does the store close?" }
11
+ * { kind: "mentions", title: "Mentions", text: "Sam (@sam) = <@123>, Alfred = <@456> (this is you)" }
12
+ * ```
13
+ *
14
+ * Bounds, enforced by the server, which refuses a request over them with what is wrong rather
15
+ * than clipping it: at most 8 items on a message, a `title` of at most 80 characters, a `text`
16
+ * of at most 4 000, and 12 000 characters in all.
17
+ */
18
+ export type MessageContextItem = {
19
+ /**
20
+ * What the item is: `reply`, `mentions`, `place` or `channel` today. The server holds the
21
+ * list and refuses an unknown kind with the real ones, so a new kind needs no SDK change.
22
+ */
23
+ kind: string;
24
+ /** What a person sees on the chip and the model on the block's line. At most 80 characters. */
25
+ title: string;
26
+ /** The content, as plain text. At most 4 000 characters. */
27
+ text: string;
28
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -4,6 +4,7 @@ export * from "./response/v5";
4
4
  export * from "./state/convo_state_response";
5
5
  export * from "./conversation/v4/conversation_v4";
6
6
  export * from "./conversation/address";
7
+ export * from "./conversation/context";
7
8
  export * from "./jobs";
8
9
  export * from "./outreach";
9
10
  export * from "./error";
@@ -20,6 +20,7 @@ __exportStar(require("./response/v5"), exports);
20
20
  __exportStar(require("./state/convo_state_response"), exports);
21
21
  __exportStar(require("./conversation/v4/conversation_v4"), exports);
22
22
  __exportStar(require("./conversation/address"), exports);
23
+ __exportStar(require("./conversation/context"), exports);
23
24
  __exportStar(require("./jobs"), exports);
24
25
  __exportStar(require("./outreach"), exports);
25
26
  __exportStar(require("./error"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@butlerbot/sdk",
3
- "version": "0.0.45",
3
+ "version": "0.0.46",
4
4
  "description": "The official ButlerBot SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",