@butlerbot/sdk 0.0.18-alpha.2 → 0.0.18

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.
Files changed (37) hide show
  1. package/dist/index.d.ts +10 -0
  2. package/dist/index.js +27 -25
  3. package/dist/link/hook.d.ts +70 -0
  4. package/dist/link/hook.js +60 -0
  5. package/dist/link/index.d.ts +11 -0
  6. package/dist/link/index.js +15 -0
  7. package/dist/link/link.d.ts +142 -0
  8. package/dist/link/link.js +427 -0
  9. package/dist/link/protocol.d.ts +186 -0
  10. package/dist/link/protocol.js +25 -0
  11. package/dist/link/schema.d.ts +80 -0
  12. package/dist/link/schema.js +88 -0
  13. package/dist/link/socket.d.ts +33 -0
  14. package/dist/link/socket.js +70 -0
  15. package/dist/link/tool.d.ts +95 -0
  16. package/dist/link/tool.js +52 -0
  17. package/dist/modules/conversation.d.ts +28 -9
  18. package/dist/modules/conversation.js +104 -122
  19. package/dist/modules/transport.d.ts +101 -0
  20. package/dist/modules/transport.js +78 -0
  21. package/dist/modules/transport_link.d.ts +28 -0
  22. package/dist/modules/transport_link.js +143 -0
  23. package/dist/modules/transport_sse.d.ts +28 -0
  24. package/dist/modules/transport_sse.js +58 -0
  25. package/dist/modules/usage.js +16 -27
  26. package/dist/types/conversation/v4/conversation_v4.d.ts +50 -0
  27. package/dist/types/conversation/v4/conversation_v4.js +2 -0
  28. package/dist/types/conversation/v4/message_v4.d.ts +85 -0
  29. package/dist/types/conversation/v4/message_v4.js +2 -0
  30. package/dist/types/state/convo_state_response.d.ts +2 -1
  31. package/dist/types/type_registry.d.ts +2 -0
  32. package/dist/types/type_registry.js +2 -0
  33. package/dist/util/emitter.d.ts +15 -0
  34. package/dist/util/emitter.js +51 -0
  35. package/dist/util/url_formatter.js +4 -1
  36. package/package.json +34 -10
  37. package/readme.md +126 -1
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { APIPath } from "./config";
2
+ import { Link, LinkOptions } from "./link";
2
3
  import { Conversation, ConversationOptions } from "./modules/conversation";
3
4
  import { UsagePolicyDataOptions } from "./modules/usage";
4
5
  type OptionalApiKey<T> = Omit<T, "apiKey"> & {
@@ -22,9 +23,18 @@ export declare class ButlerBotClient {
22
23
  healthCheck(healthCheckPath?: string): Promise<boolean>;
23
24
  /** Spawns a new Conversation, inherits api key and server URL */
24
25
  createConversation<V extends APIPath = "v4">(config?: OptionalApiKey<ConversationOptions<V>>): Conversation<V>;
26
+ /**
27
+ * Creates a Link: a live connection that can register tools and hooks, and carry
28
+ * conversations. Inherits the client's API key and server URL.
29
+ */
30
+ createLink(config: OptionalApiKey<LinkOptions>): Link;
25
31
  /** Get current usage policy data */
26
32
  getUsagePolicyData(config: OptionalApiKey<UsagePolicyDataOptions>): Promise<import("./types/usage/policy").UsagePolicyData>;
27
33
  }
28
34
  export * from "./types/type_registry";
35
+ export * from "./link";
29
36
  export { Conversation, ConversationOptions };
30
37
  export type { APIPath };
38
+ export type { ConversationStream, ConversationTransport, TransportTurnRequest, TransportHandlers, } from "./modules/transport";
39
+ export { LinkConversationTransport } from "./modules/transport_link";
40
+ export { SSEConversationTransport } from "./modules/transport_sse";
package/dist/index.js CHANGED
@@ -13,18 +13,10 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
13
13
  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
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
18
- return new (P || (P = Promise))(function (resolve, reject) {
19
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
22
- step((generator = generator.apply(thisArg, _arguments || [])).next());
23
- });
24
- };
25
16
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Conversation = exports.ButlerBotClient = void 0;
17
+ exports.SSEConversationTransport = exports.LinkConversationTransport = exports.Conversation = exports.ButlerBotClient = void 0;
27
18
  const config_1 = require("./config");
19
+ const link_1 = require("./link");
28
20
  const conversation_1 = require("./modules/conversation");
29
21
  Object.defineProperty(exports, "Conversation", { enumerable: true, get: function () { return conversation_1.Conversation; } });
30
22
  const usage_1 = require("./modules/usage");
@@ -35,29 +27,39 @@ class ButlerBotClient {
35
27
  this.debug = config.debug || false;
36
28
  }
37
29
  /** Checks the health of the server returning true if server is alive */
38
- healthCheck() {
39
- return __awaiter(this, arguments, void 0, function* (healthCheckPath = config_1.CONFIG.healthcheckPath) {
40
- const url = `${this.serverUrl}${healthCheckPath}`;
41
- try {
42
- const response = yield fetch(url);
43
- return response.ok;
44
- }
45
- catch (error) {
46
- if (this.debug)
47
- console.warn("[Healthcheck Failure]", error);
48
- return false;
49
- }
50
- });
30
+ async healthCheck(healthCheckPath = config_1.CONFIG.healthcheckPath) {
31
+ const url = `${this.serverUrl}${healthCheckPath}`;
32
+ try {
33
+ const response = await fetch(url);
34
+ return response.ok;
35
+ }
36
+ catch (error) {
37
+ if (this.debug)
38
+ console.warn("[Healthcheck Failure]", error);
39
+ return false;
40
+ }
51
41
  }
52
42
  /** Spawns a new Conversation, inherits api key and server URL */
53
43
  createConversation(config = {}) {
54
- return new conversation_1.Conversation(Object.assign({ debug: this.debug, apiKey: this.apiKey, serverUrl: this.serverUrl }, config));
44
+ return new conversation_1.Conversation({ debug: this.debug, apiKey: this.apiKey, serverUrl: this.serverUrl, ...config });
45
+ }
46
+ /**
47
+ * Creates a Link: a live connection that can register tools and hooks, and carry
48
+ * conversations. Inherits the client's API key and server URL.
49
+ */
50
+ createLink(config) {
51
+ return new link_1.Link({ debug: this.debug, apiKey: this.apiKey, serverUrl: this.serverUrl, ...config });
55
52
  }
56
53
  /** Get current usage policy data */
57
54
  getUsagePolicyData(config) {
58
- return (0, usage_1.getUsagePolicyData)(Object.assign({ serverURL: this.serverUrl, apiKey: this.apiKey, debug: this.debug }, config));
55
+ return (0, usage_1.getUsagePolicyData)({ serverURL: this.serverUrl, apiKey: this.apiKey, debug: this.debug, ...config });
59
56
  }
60
57
  }
61
58
  exports.ButlerBotClient = ButlerBotClient;
62
59
  // Expose types from subsequent modules
63
60
  __exportStar(require("./types/type_registry"), exports);
61
+ __exportStar(require("./link"), exports);
62
+ var transport_link_1 = require("./modules/transport_link");
63
+ Object.defineProperty(exports, "LinkConversationTransport", { enumerable: true, get: function () { return transport_link_1.LinkConversationTransport; } });
64
+ var transport_sse_1 = require("./modules/transport_sse");
65
+ Object.defineProperty(exports, "SSEConversationTransport", { enumerable: true, get: function () { return transport_sse_1.SSEConversationTransport; } });
@@ -0,0 +1,70 @@
1
+ import { LinkHookDeclaration, LinkHookEventDeclaration } from "./protocol";
2
+ import { JSONSchema, ToolSchema } from "./schema";
3
+ export type HookConfig<S extends ToolSchema | undefined> = {
4
+ /**
5
+ * This hook's id within the link. The public id becomes `link:<linkId>/<id>`,
6
+ * which the user's background agents are subscribed to — so treat it as permanent.
7
+ */
8
+ id: string;
9
+ /** Human name, shown wherever hooks are listed. */
10
+ name: string;
11
+ /** What makes this hook fire. Read by whoever wires an agent up to it. */
12
+ description: string;
13
+ /** The events this hook can emit. Emitting an undeclared event is refused. */
14
+ events: LinkHookEventDeclaration[];
15
+ /**
16
+ * What an agent subscribing to this hook can configure — a filter, a threshold.
17
+ * A zod 4 schema, any Standard Schema, or a plain JSON Schema object.
18
+ */
19
+ schema?: S;
20
+ /** JSON Schema to send instead of deriving it from `schema`. */
21
+ jsonSchema?: JSONSchema;
22
+ };
23
+ /**
24
+ * A hook of any schema, which is what a link holds. See `AnyTool` for why this
25
+ * exists rather than a `Hook<...>`.
26
+ */
27
+ export interface AnyHook {
28
+ readonly id: string;
29
+ sourceId?: string;
30
+ declaration(): LinkHookDeclaration;
31
+ attach(link: HookEmitter): void;
32
+ }
33
+ /** What a hook needs from its link in order to emit. Implemented by `Link`. */
34
+ export type HookEmitter = {
35
+ /**
36
+ * Emits on the hook with this local id. The link resolves the public source id,
37
+ * which only exists once the hook has been registered.
38
+ */
39
+ emitHook(hookId: string, event: string, payload?: Record<string, unknown>, ownerId?: string): Promise<void>;
40
+ };
41
+ /**
42
+ * A source of events that can wake the user's background agents.
43
+ *
44
+ * A hook does not run anything itself: it announces that something happened, and
45
+ * the agents the user has subscribed to it decide what to do about that.
46
+ */
47
+ export declare class Hook<S extends ToolSchema | undefined = undefined> {
48
+ private readonly config;
49
+ readonly id: string;
50
+ /** The public id (`link:<linkId>/<id>`), known once the link has registered it. */
51
+ sourceId?: string;
52
+ private link?;
53
+ constructor(config: HookConfig<S>);
54
+ get name(): string;
55
+ get events(): LinkHookEventDeclaration[];
56
+ /** The declaration sent to the server. */
57
+ declaration(): LinkHookDeclaration;
58
+ /** Called by `Link.addHook`. */
59
+ attach(link: HookEmitter): void;
60
+ /**
61
+ * Announces that one of this hook's events happened.
62
+ *
63
+ * Waits for the link to be connected if it is still coming up, so a hook can
64
+ * fire during startup without the caller sequencing it by hand.
65
+ *
66
+ * @param ownerId Global-scope links only: the user this event concerns. A
67
+ * user-scoped link is always stamped with its own owner and may not name another.
68
+ */
69
+ emit(event: string, payload?: Record<string, unknown>, ownerId?: string): Promise<void>;
70
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Hook = void 0;
4
+ const schema_1 = require("./schema");
5
+ /**
6
+ * A source of events that can wake the user's background agents.
7
+ *
8
+ * A hook does not run anything itself: it announces that something happened, and
9
+ * the agents the user has subscribed to it decide what to do about that.
10
+ */
11
+ class Hook {
12
+ constructor(config) {
13
+ this.config = config;
14
+ this.id = config.id;
15
+ }
16
+ get name() {
17
+ return this.config.name;
18
+ }
19
+ get events() {
20
+ return this.config.events;
21
+ }
22
+ /** The declaration sent to the server. */
23
+ declaration() {
24
+ return {
25
+ localId: this.id,
26
+ name: this.config.name,
27
+ description: this.config.description,
28
+ argsSchema: this.config.jsonSchema
29
+ ?? (this.config.schema ? (0, schema_1.toJSONSchema)(this.config.schema, this.id) : { type: "object", properties: {} }),
30
+ events: this.config.events,
31
+ };
32
+ }
33
+ /** Called by `Link.addHook`. */
34
+ attach(link) {
35
+ this.link = link;
36
+ }
37
+ /**
38
+ * Announces that one of this hook's events happened.
39
+ *
40
+ * Waits for the link to be connected if it is still coming up, so a hook can
41
+ * fire during startup without the caller sequencing it by hand.
42
+ *
43
+ * @param ownerId Global-scope links only: the user this event concerns. A
44
+ * user-scoped link is always stamped with its own owner and may not name another.
45
+ */
46
+ async emit(event, payload, ownerId) {
47
+ if (!this.link) {
48
+ throw new Error(`Hook "${this.id}" is not on a link yet — call link.addHook(hook) first.`);
49
+ }
50
+ // Caught here rather than on the wire: a typo should fail where it was made.
51
+ if (!this.config.events.some(declared => declared.name === event)) {
52
+ const declared = this.config.events.map(entry => entry.name).join(", ") || "none";
53
+ throw new Error(`Hook "${this.id}" does not declare an event named "${event}". Declared: ${declared}.`);
54
+ }
55
+ // The local id, not `sourceId`: emitting during startup gets here before
56
+ // registration has assigned one, and the link fills it in once it knows.
57
+ await this.link.emitHook(this.id, event, payload, ownerId);
58
+ }
59
+ }
60
+ exports.Hook = Hook;
@@ -0,0 +1,11 @@
1
+ export { Link } from "./link";
2
+ export type { LinkOptions, LinkEvents, LinkState, ExchangeOptions } from "./link";
3
+ export { Tool } from "./tool";
4
+ export type { AnyTool, ToolConfig, ToolRunContext, ToolCallMeta, ToolStatusReporter, ToolInvocation } from "./tool";
5
+ export { Hook } from "./hook";
6
+ export type { AnyHook, HookConfig, HookEmitter } from "./hook";
7
+ export { LINK_PROTOCOL_VERSION, LinkError } from "./protocol";
8
+ export type { LinkClientFrame, LinkClientFrameOf, LinkClientFrameType, LinkServerFrame, LinkServerFrameOf, LinkServerFrameType, LinkScopeKind, LinkToolDescriptor, LinkHookDeclaration, LinkHookEventDeclaration, } from "./protocol";
9
+ export type { JSONSchema, StandardSchemaV1, ToolSchema, InferSchemaOutput } from "./schema";
10
+ export { buildHandshake, defaultSocketFactory } from "./socket";
11
+ export type { SocketFactory, SocketConnection, SocketHandlers } from "./socket";
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultSocketFactory = exports.buildHandshake = exports.LinkError = exports.LINK_PROTOCOL_VERSION = exports.Hook = exports.Tool = exports.Link = void 0;
4
+ var link_1 = require("./link");
5
+ Object.defineProperty(exports, "Link", { enumerable: true, get: function () { return link_1.Link; } });
6
+ var tool_1 = require("./tool");
7
+ Object.defineProperty(exports, "Tool", { enumerable: true, get: function () { return tool_1.Tool; } });
8
+ var hook_1 = require("./hook");
9
+ Object.defineProperty(exports, "Hook", { enumerable: true, get: function () { return hook_1.Hook; } });
10
+ var protocol_1 = require("./protocol");
11
+ Object.defineProperty(exports, "LINK_PROTOCOL_VERSION", { enumerable: true, get: function () { return protocol_1.LINK_PROTOCOL_VERSION; } });
12
+ Object.defineProperty(exports, "LinkError", { enumerable: true, get: function () { return protocol_1.LinkError; } });
13
+ var socket_1 = require("./socket");
14
+ Object.defineProperty(exports, "buildHandshake", { enumerable: true, get: function () { return socket_1.buildHandshake; } });
15
+ Object.defineProperty(exports, "defaultSocketFactory", { enumerable: true, get: function () { return socket_1.defaultSocketFactory; } });
@@ -0,0 +1,142 @@
1
+ import { AnyHook } from "./hook";
2
+ import { LinkClientFrameType, LinkClientPayloads, LinkScopeKind, LinkServerFrame } from "./protocol";
3
+ import { SocketFactory } from "./socket";
4
+ import { AnyTool } from "./tool";
5
+ export type LinkState = "idle" | "connecting" | "open" | "closed";
6
+ export type LinkEvents = {
7
+ /** The link is connected and everything it holds has been registered. */
8
+ connect: [{
9
+ connectionId: string;
10
+ scope: LinkScopeKind;
11
+ }];
12
+ disconnect: [{
13
+ code: number;
14
+ reason: string;
15
+ willReconnect: boolean;
16
+ }];
17
+ /** A protocol or transport error. Fatal ones also stop reconnection. */
18
+ error: [Error];
19
+ /** Server-side chatter, useful when debugging. */
20
+ log: [string];
21
+ /** The server is shutting this connection down deliberately. */
22
+ goodbye: [{
23
+ reason: string;
24
+ reconnectAfterMs: number;
25
+ }];
26
+ };
27
+ export type LinkOptions = {
28
+ /** A user API key, or the service key for a global link. */
29
+ apiKey: string;
30
+ /**
31
+ * This link's name, chosen by you and stable forever.
32
+ *
33
+ * Every id the link creates is derived from it, and those ids are what the
34
+ * user's tool settings and background agent subscriptions point at — so changing
35
+ * it silently orphans both. Pick a deliberate constant ("coffee-machine"), never
36
+ * a hostname, a version, or a value generated at startup.
37
+ *
38
+ * Two live connections claiming the same linkId is last-writer-wins: the newer
39
+ * one takes over and the older one's registrations are released.
40
+ */
41
+ linkId: string;
42
+ serverUrl?: string;
43
+ /** Informational, shown in server logs. Defaults to the SDK name. */
44
+ client?: string;
45
+ debug?: boolean;
46
+ /** Reconnect automatically with backoff. Default true. */
47
+ reconnect?: boolean;
48
+ minReconnectDelayMs?: number;
49
+ maxReconnectDelayMs?: number;
50
+ /** Keeps idle connections alive through proxies. 0 disables. Default 30s. */
51
+ heartbeatMs?: number;
52
+ /** How long to wait for an acknowledgement. Turns are never timed out here. */
53
+ requestTimeoutMs?: number;
54
+ socketFactory?: SocketFactory;
55
+ };
56
+ export type ExchangeOptions = {
57
+ /** Which reply ends the exchange. Defaults to an `ack`. */
58
+ isDone?(frame: LinkServerFrame): boolean;
59
+ /** Called for every other reply, in order. */
60
+ onFrame?(frame: LinkServerFrame): void;
61
+ /** 0 waits forever, which is what a conversation turn needs. */
62
+ timeoutMs?: number;
63
+ /** Set false only for the handshake itself. */
64
+ awaitReady?: boolean;
65
+ };
66
+ /**
67
+ * A live connection to Alfred that carries tools, hooks and conversations.
68
+ *
69
+ * The server keeps no record of a link between connections: everything is
70
+ * re-declared on connect, and ids are derived from your `linkId`, so a reconnect
71
+ * anywhere lands on the same saved settings and subscriptions.
72
+ */
73
+ export declare class Link {
74
+ private readonly options;
75
+ private readonly emitter;
76
+ private readonly tools;
77
+ private readonly hooks;
78
+ private readonly pending;
79
+ private readonly calls;
80
+ private socket;
81
+ private frameCounter;
82
+ private currentState;
83
+ private identity?;
84
+ private connecting?;
85
+ private readySignal?;
86
+ private reconnectAttempt;
87
+ private reconnectAfterMs;
88
+ private heartbeat?;
89
+ private closedByUs;
90
+ constructor(options: LinkOptions);
91
+ /** Adds a tool Alfred can call. Registered on connect, or immediately if already open. */
92
+ addTool(tool: AnyTool): this;
93
+ /** Adds a hook that can wake the user's background agents. */
94
+ addHook(hook: AnyHook): this;
95
+ getTool(id: string): AnyTool | undefined;
96
+ getHook(id: string): AnyHook | undefined;
97
+ get state(): LinkState;
98
+ get linkId(): string;
99
+ /** The ephemeral id of this connection. Changes on every reconnect. */
100
+ get connectionId(): string | undefined;
101
+ /** Whether this link speaks for one user or for the whole service. */
102
+ get scope(): LinkScopeKind | undefined;
103
+ on<K extends keyof LinkEvents>(event: K, listener: (...args: LinkEvents[K]) => unknown): string;
104
+ off<K extends keyof LinkEvents>(event: K, id: string): void;
105
+ /** Connects, resolving once every tool and hook has been registered. */
106
+ connect(): Promise<this>;
107
+ /** Resolves when the link is usable, connecting first if it has not been asked to yet. */
108
+ ready(): Promise<void>;
109
+ /** Closes for good. Registrations are released server-side as the socket drops. */
110
+ close(reason?: string): void;
111
+ private openSocket;
112
+ private onOpen;
113
+ private onClose;
114
+ /**
115
+ * Reconnects with full jitter on top of any delay the server asked for.
116
+ *
117
+ * A fleet told to reconnect must not come back in unison, which is exactly what
118
+ * a fixed delay produces.
119
+ */
120
+ private scheduleReconnect;
121
+ private startHeartbeat;
122
+ private stopHeartbeat;
123
+ private registerAll;
124
+ private registerTools;
125
+ private registerHook;
126
+ /** Called by `Hook.emit`. */
127
+ emitHook(hookId: string, event: string, payload?: Record<string, unknown>, ownerId?: string): Promise<void>;
128
+ /** Sends a frame without waiting for anything. Returns its id. */
129
+ send<T extends LinkClientFrameType>(type: T, payload: LinkClientPayloads[T], replyTo?: string): string;
130
+ /**
131
+ * Sends a frame and waits for the reply that ends it.
132
+ *
133
+ * Intermediate replies (a turn's events, a status update) go to `onFrame`, and an
134
+ * `error` frame rejects — so a caller handles one outcome, not a stream of maybes.
135
+ */
136
+ exchange<T extends LinkClientFrameType>(type: T, payload: LinkClientPayloads[T], options?: ExchangeOptions): Promise<LinkServerFrame>;
137
+ private settle;
138
+ private failPending;
139
+ private onMessage;
140
+ private handleToolCall;
141
+ private debug;
142
+ }