@butlerbot/sdk 0.0.34 → 0.0.35
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/config.d.ts +10 -0
- package/dist/config.js +10 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +46 -1
- package/dist/modules/api_request.d.ts +18 -0
- package/dist/modules/api_request.js +44 -0
- package/dist/modules/jobs.d.ts +48 -0
- package/dist/modules/jobs.js +72 -0
- package/dist/modules/outreach.d.ts +31 -0
- package/dist/modules/outreach.js +39 -0
- package/dist/types/jobs/index.d.ts +1 -0
- package/dist/types/jobs/index.js +17 -0
- package/dist/types/jobs/job.d.ts +179 -0
- package/dist/types/jobs/job.js +30 -0
- package/dist/types/outreach/delivery.d.ts +78 -0
- package/dist/types/outreach/delivery.js +21 -0
- package/dist/types/outreach/index.d.ts +1 -0
- package/dist/types/outreach/index.js +17 -0
- package/dist/types/type_registry.d.ts +2 -0
- package/dist/types/type_registry.js +2 -0
- package/dist/util/api_error.d.ts +34 -0
- package/dist/util/api_error.js +42 -0
- package/package.json +1 -1
- package/readme.md +65 -0
package/dist/config.d.ts
CHANGED
|
@@ -59,6 +59,16 @@ export declare const CONFIG: {
|
|
|
59
59
|
};
|
|
60
60
|
};
|
|
61
61
|
};
|
|
62
|
+
jobs: {
|
|
63
|
+
/** The collection. One job is `${base}/${jobId}`, its cancel `${base}/${jobId}/cancel`. */
|
|
64
|
+
base: string;
|
|
65
|
+
/** The owner's job settings, which live on the user rather than on a job. */
|
|
66
|
+
settings: string;
|
|
67
|
+
};
|
|
68
|
+
outreach: {
|
|
69
|
+
/** The inbox. One delivery's answer is `${base}/${deliveryId}/answer`. */
|
|
70
|
+
base: string;
|
|
71
|
+
};
|
|
62
72
|
};
|
|
63
73
|
};
|
|
64
74
|
export type APIPath = keyof typeof CONFIG.paths.conversation;
|
package/dist/config.js
CHANGED
|
@@ -50,6 +50,16 @@ exports.CONFIG = {
|
|
|
50
50
|
policy: {
|
|
51
51
|
v3: { base: "/api/user/usage/v3/policy" },
|
|
52
52
|
}
|
|
53
|
+
},
|
|
54
|
+
jobs: {
|
|
55
|
+
/** The collection. One job is `${base}/${jobId}`, its cancel `${base}/${jobId}/cancel`. */
|
|
56
|
+
base: "/api/jobs",
|
|
57
|
+
/** The owner's job settings, which live on the user rather than on a job. */
|
|
58
|
+
settings: "/api/user/jobs-settings",
|
|
59
|
+
},
|
|
60
|
+
outreach: {
|
|
61
|
+
/** The inbox. One delivery's answer is `${base}/${deliveryId}/answer`. */
|
|
62
|
+
base: "/api/outreach",
|
|
53
63
|
}
|
|
54
64
|
}
|
|
55
65
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { APIPath } from "./config";
|
|
|
2
2
|
import { Link, LinkOptions } from "./link";
|
|
3
3
|
import { Conversation, ConversationOptions } from "./modules/conversation";
|
|
4
4
|
import { UsagePolicyDataOptions } from "./modules/usage";
|
|
5
|
+
import { type CancelJobOptions, type GetJobOptions, type ListJobsOptions, type UpdateJobOptions, type UpdateJobSettingsOptions } from "./modules/jobs";
|
|
6
|
+
import { type AnswerDeliveryOptions, type ListDeliveriesOptions } from "./modules/outreach";
|
|
5
7
|
type OptionalApiKey<T> = Omit<T, "apiKey"> & {
|
|
6
8
|
/** Optional API key, defaults to API key specified in client */
|
|
7
9
|
apiKey?: string;
|
|
@@ -42,6 +44,22 @@ export declare class ButlerBotClient {
|
|
|
42
44
|
createLink(config: OptionalApiKey<LinkOptions>): Link;
|
|
43
45
|
/** Get current usage policy data */
|
|
44
46
|
getUsagePolicyData(config: OptionalApiKey<UsagePolicyDataOptions>): Promise<import("./types/usage/policy").UsagePolicyData>;
|
|
47
|
+
/** A page of this user's jobs, with what their jobs may spend today */
|
|
48
|
+
listJobs(config?: OptionalApiKey<ListJobsOptions>): Promise<import("./types/type_registry").JobListResponse>;
|
|
49
|
+
/** One job, with its plan, its journal and the questions waiting on the user */
|
|
50
|
+
getJob(config: OptionalApiKey<GetJobOptions>): Promise<import("./types/type_registry").JobDetailResponse>;
|
|
51
|
+
/** Stops a job. Throws a `ButlerBotAPIError` with `isConflict` when it had already finished */
|
|
52
|
+
cancelJob(config: OptionalApiKey<CancelJobOptions>): Promise<import("./types/type_registry").JobCancelResponse>;
|
|
53
|
+
/** Changes one job's autonomy: how far it may act, until when, and what it always asks about */
|
|
54
|
+
updateJob(config: OptionalApiKey<UpdateJobOptions>): Promise<import("./types/type_registry").JobUpdateResponse>;
|
|
55
|
+
/** Changes this user's job settings: their daily allowance and what new jobs start with */
|
|
56
|
+
updateJobSettings(config?: OptionalApiKey<UpdateJobSettingsOptions>): Promise<import("./types/type_registry").JobSettingsResponse>;
|
|
57
|
+
/** A page of the inbox: what Alfred has told or asked this user outside a chat */
|
|
58
|
+
listDeliveries(config?: OptionalApiKey<ListDeliveriesOptions>): Promise<import("./types/type_registry").DeliveryListResponse>;
|
|
59
|
+
/** Answers a delivery. Throws a `ButlerBotAPIError` with `isConflict` when it was already answered */
|
|
60
|
+
answerDelivery(config: OptionalApiKey<AnswerDeliveryOptions>): Promise<import("./types/type_registry").DeliveryAnswerResponse>;
|
|
61
|
+
/** The client's own server and key underneath whatever the call named itself. */
|
|
62
|
+
private forRequest;
|
|
45
63
|
}
|
|
46
64
|
export * from "./types/type_registry";
|
|
47
65
|
export * from "./link";
|
|
@@ -49,6 +67,11 @@ export { Conversation };
|
|
|
49
67
|
export type { ConversationOptions };
|
|
50
68
|
export type { APIPath };
|
|
51
69
|
export type { ConversationStream, ConversationTransport, TransportTurnRequest, TransportHandlers, } from "./modules/transport";
|
|
70
|
+
export { ButlerBotAPIError } from "./util/api_error";
|
|
71
|
+
export { listJobs, getJob, cancelJob, updateJob, updateJobSettings } from "./modules/jobs";
|
|
72
|
+
export type { JobsRequestOptions, ListJobsOptions, GetJobOptions, CancelJobOptions, UpdateJobOptions, UpdateJobSettingsOptions, } from "./modules/jobs";
|
|
73
|
+
export { listDeliveries, answerDelivery } from "./modules/outreach";
|
|
74
|
+
export type { OutreachRequestOptions, ListDeliveriesOptions, AnswerDeliveryOptions, } from "./modules/outreach";
|
|
52
75
|
export { LinkConversationTransport } from "./modules/transport_link";
|
|
53
76
|
export { SSEConversationTransport } from "./modules/transport_sse";
|
|
54
77
|
export type { SteerResult, TurnStopMode, TurnStopped } from "./modules/transport";
|
package/dist/index.js
CHANGED
|
@@ -14,12 +14,14 @@ 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.Conversation = exports.ButlerBotClient = void 0;
|
|
17
|
+
exports.SSEConversationTransport = exports.LinkConversationTransport = exports.answerDelivery = exports.listDeliveries = exports.updateJobSettings = exports.updateJob = 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");
|
|
21
21
|
Object.defineProperty(exports, "Conversation", { enumerable: true, get: function () { return conversation_1.Conversation; } });
|
|
22
22
|
const usage_1 = require("./modules/usage");
|
|
23
|
+
const jobs_1 = require("./modules/jobs");
|
|
24
|
+
const outreach_1 = require("./modules/outreach");
|
|
23
25
|
/**
|
|
24
26
|
* The options a caller actually gave, with the keys they left out removed.
|
|
25
27
|
*
|
|
@@ -70,11 +72,54 @@ class ButlerBotClient {
|
|
|
70
72
|
getUsagePolicyData(config) {
|
|
71
73
|
return (0, usage_1.getUsagePolicyData)({ serverURL: this.serverUrl, apiKey: this.apiKey, debug: this.debug, ...config });
|
|
72
74
|
}
|
|
75
|
+
/** A page of this user's jobs, with what their jobs may spend today */
|
|
76
|
+
listJobs(config = {}) {
|
|
77
|
+
return (0, jobs_1.listJobs)(this.forRequest(config));
|
|
78
|
+
}
|
|
79
|
+
/** One job, with its plan, its journal and the questions waiting on the user */
|
|
80
|
+
getJob(config) {
|
|
81
|
+
return (0, jobs_1.getJob)(this.forRequest(config));
|
|
82
|
+
}
|
|
83
|
+
/** Stops a job. Throws a `ButlerBotAPIError` with `isConflict` when it had already finished */
|
|
84
|
+
cancelJob(config) {
|
|
85
|
+
return (0, jobs_1.cancelJob)(this.forRequest(config));
|
|
86
|
+
}
|
|
87
|
+
/** Changes one job's autonomy: how far it may act, until when, and what it always asks about */
|
|
88
|
+
updateJob(config) {
|
|
89
|
+
return (0, jobs_1.updateJob)(this.forRequest(config));
|
|
90
|
+
}
|
|
91
|
+
/** Changes this user's job settings: their daily allowance and what new jobs start with */
|
|
92
|
+
updateJobSettings(config = {}) {
|
|
93
|
+
return (0, jobs_1.updateJobSettings)(this.forRequest(config));
|
|
94
|
+
}
|
|
95
|
+
/** A page of the inbox: what Alfred has told or asked this user outside a chat */
|
|
96
|
+
listDeliveries(config = {}) {
|
|
97
|
+
return (0, outreach_1.listDeliveries)(this.forRequest(config));
|
|
98
|
+
}
|
|
99
|
+
/** Answers a delivery. Throws a `ButlerBotAPIError` with `isConflict` when it was already answered */
|
|
100
|
+
answerDelivery(config) {
|
|
101
|
+
return (0, outreach_1.answerDelivery)(this.forRequest(config));
|
|
102
|
+
}
|
|
103
|
+
/** The client's own server and key underneath whatever the call named itself. */
|
|
104
|
+
forRequest(config) {
|
|
105
|
+
return { serverURL: this.serverUrl, apiKey: this.apiKey, debug: this.debug, ...given(config) };
|
|
106
|
+
}
|
|
73
107
|
}
|
|
74
108
|
exports.ButlerBotClient = ButlerBotClient;
|
|
75
109
|
// Expose types from subsequent modules
|
|
76
110
|
__exportStar(require("./types/type_registry"), exports);
|
|
77
111
|
__exportStar(require("./link"), exports);
|
|
112
|
+
var api_error_1 = require("./util/api_error");
|
|
113
|
+
Object.defineProperty(exports, "ButlerBotAPIError", { enumerable: true, get: function () { return api_error_1.ButlerBotAPIError; } });
|
|
114
|
+
var jobs_2 = require("./modules/jobs");
|
|
115
|
+
Object.defineProperty(exports, "listJobs", { enumerable: true, get: function () { return jobs_2.listJobs; } });
|
|
116
|
+
Object.defineProperty(exports, "getJob", { enumerable: true, get: function () { return jobs_2.getJob; } });
|
|
117
|
+
Object.defineProperty(exports, "cancelJob", { enumerable: true, get: function () { return jobs_2.cancelJob; } });
|
|
118
|
+
Object.defineProperty(exports, "updateJob", { enumerable: true, get: function () { return jobs_2.updateJob; } });
|
|
119
|
+
Object.defineProperty(exports, "updateJobSettings", { enumerable: true, get: function () { return jobs_2.updateJobSettings; } });
|
|
120
|
+
var outreach_2 = require("./modules/outreach");
|
|
121
|
+
Object.defineProperty(exports, "listDeliveries", { enumerable: true, get: function () { return outreach_2.listDeliveries; } });
|
|
122
|
+
Object.defineProperty(exports, "answerDelivery", { enumerable: true, get: function () { return outreach_2.answerDelivery; } });
|
|
78
123
|
var transport_link_1 = require("./modules/transport_link");
|
|
79
124
|
Object.defineProperty(exports, "LinkConversationTransport", { enumerable: true, get: function () { return transport_link_1.LinkConversationTransport; } });
|
|
80
125
|
var transport_sse_1 = require("./modules/transport_sse");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type APIRequestOptions = {
|
|
2
|
+
url: string;
|
|
3
|
+
/** GET when nothing says otherwise. */
|
|
4
|
+
method?: "GET" | "POST" | "PATCH" | "DELETE";
|
|
5
|
+
/** Sent as JSON. A route that takes no body is called without one. */
|
|
6
|
+
body?: unknown;
|
|
7
|
+
/** What the caller was trying to do, for the message a failure carries. */
|
|
8
|
+
action: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* One call to a JSON route, with the failure turned into a `ButlerBotAPIError`.
|
|
12
|
+
*
|
|
13
|
+
* The body is read as text once and parsed here rather than through `response.json()`, because
|
|
14
|
+
* a failing route's body has to be readable both as the parsed `error`/`errormessage` and as
|
|
15
|
+
* whatever else it carries — the job on a rejected cancel, the delivery on a rejected answer —
|
|
16
|
+
* and a response body can only be consumed once.
|
|
17
|
+
*/
|
|
18
|
+
export declare function requestAPI<T>(options: APIRequestOptions): Promise<T>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestAPI = requestAPI;
|
|
4
|
+
const api_error_1 = require("../util/api_error");
|
|
5
|
+
/**
|
|
6
|
+
* One call to a JSON route, with the failure turned into a `ButlerBotAPIError`.
|
|
7
|
+
*
|
|
8
|
+
* The body is read as text once and parsed here rather than through `response.json()`, because
|
|
9
|
+
* a failing route's body has to be readable both as the parsed `error`/`errormessage` and as
|
|
10
|
+
* whatever else it carries — the job on a rejected cancel, the delivery on a rejected answer —
|
|
11
|
+
* and a response body can only be consumed once.
|
|
12
|
+
*/
|
|
13
|
+
async function requestAPI(options) {
|
|
14
|
+
const init = { method: options.method || "GET" };
|
|
15
|
+
if (options.body !== undefined) {
|
|
16
|
+
init.headers = { "Content-Type": "application/json" };
|
|
17
|
+
init.body = JSON.stringify(options.body);
|
|
18
|
+
}
|
|
19
|
+
const response = await fetch(options.url, init);
|
|
20
|
+
const text = await response.text();
|
|
21
|
+
const data = parseJSON(text);
|
|
22
|
+
const envelope = (data && typeof data === "object" ? data : {});
|
|
23
|
+
if (!response.ok || envelope.success !== true) {
|
|
24
|
+
throw new api_error_1.ButlerBotAPIError({
|
|
25
|
+
action: options.action,
|
|
26
|
+
status: response.status,
|
|
27
|
+
statusText: response.statusText,
|
|
28
|
+
error: envelope.error,
|
|
29
|
+
errormessage: envelope.errormessage,
|
|
30
|
+
body: data ?? text,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
function parseJSON(text) {
|
|
36
|
+
if (!text)
|
|
37
|
+
return undefined;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { JobAutonomy, JobCancelResponse, JobDetailResponse, JobListResponse, JobSettingsResponse, JobSettingsUpdate, JobStatus, JobUpdateResponse } from "../types/jobs";
|
|
2
|
+
/** What every jobs call needs: where the server is, and who is asking. */
|
|
3
|
+
export type JobsRequestOptions = {
|
|
4
|
+
serverURL?: string;
|
|
5
|
+
/** The base path for jobs, when it is not the default. */
|
|
6
|
+
path?: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type ListJobsOptions = JobsRequestOptions & {
|
|
11
|
+
/** 1-based. The first page when it is not given. */
|
|
12
|
+
page?: number;
|
|
13
|
+
/** How many jobs per page. */
|
|
14
|
+
limit?: number;
|
|
15
|
+
/** Only jobs in this status. */
|
|
16
|
+
status?: JobStatus;
|
|
17
|
+
};
|
|
18
|
+
export type GetJobOptions = JobsRequestOptions & {
|
|
19
|
+
jobId: string;
|
|
20
|
+
};
|
|
21
|
+
export type CancelJobOptions = JobsRequestOptions & {
|
|
22
|
+
jobId: string;
|
|
23
|
+
};
|
|
24
|
+
export type UpdateJobOptions = JobsRequestOptions & {
|
|
25
|
+
jobId: string;
|
|
26
|
+
/** How far this job may act outward on its own. */
|
|
27
|
+
autonomy?: JobAutonomy;
|
|
28
|
+
/** When that autonomy lapses back to asking. Null clears it. */
|
|
29
|
+
autonomyUntil?: string | number | null;
|
|
30
|
+
/** Things this job asks about however free it otherwise is. */
|
|
31
|
+
alwaysAsk?: string[];
|
|
32
|
+
};
|
|
33
|
+
export type UpdateJobSettingsOptions = JobsRequestOptions & JobSettingsUpdate;
|
|
34
|
+
/** A page of the caller's jobs, newest first, with what their jobs may spend today. */
|
|
35
|
+
export declare function listJobs(options: ListJobsOptions): Promise<JobListResponse>;
|
|
36
|
+
/** One job with its plan, its journal and the questions of its nobody has answered. */
|
|
37
|
+
export declare function getJob(options: GetJobOptions): Promise<JobDetailResponse>;
|
|
38
|
+
/**
|
|
39
|
+
* Stops a job.
|
|
40
|
+
*
|
|
41
|
+
* A job that had already finished is a 409: the call throws a `ButlerBotAPIError` whose
|
|
42
|
+
* `isConflict` is true and whose `body` still carries the job as it stands.
|
|
43
|
+
*/
|
|
44
|
+
export declare function cancelJob(options: CancelJobOptions): Promise<JobCancelResponse>;
|
|
45
|
+
/** Changes one job's autonomy: how far it may act, until when, and what it always asks about. */
|
|
46
|
+
export declare function updateJob(options: UpdateJobOptions): Promise<JobUpdateResponse>;
|
|
47
|
+
/** Changes the owner's job settings: their daily allowance and the autonomy new jobs start with. */
|
|
48
|
+
export declare function updateJobSettings(options: UpdateJobSettingsOptions): Promise<JobSettingsResponse>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listJobs = listJobs;
|
|
4
|
+
exports.getJob = getJob;
|
|
5
|
+
exports.cancelJob = cancelJob;
|
|
6
|
+
exports.updateJob = updateJob;
|
|
7
|
+
exports.updateJobSettings = updateJobSettings;
|
|
8
|
+
const config_1 = require("../config");
|
|
9
|
+
const url_formatter_1 = require("../util/url_formatter");
|
|
10
|
+
const api_request_1 = require("./api_request");
|
|
11
|
+
function jobsBase(options) {
|
|
12
|
+
return (options.serverURL || config_1.CONFIG.server) + (options.path || config_1.CONFIG.paths.jobs.base);
|
|
13
|
+
}
|
|
14
|
+
/** Only what was actually given: `URLSearchParams` would otherwise send the word "undefined". */
|
|
15
|
+
function query(params) {
|
|
16
|
+
const given = {};
|
|
17
|
+
for (const [key, value] of Object.entries(params)) {
|
|
18
|
+
if (value !== undefined)
|
|
19
|
+
given[key] = String(value);
|
|
20
|
+
}
|
|
21
|
+
return given;
|
|
22
|
+
}
|
|
23
|
+
/** Only the keys a caller set, so an omitted field is not sent as an explicit `undefined`. */
|
|
24
|
+
function body(fields) {
|
|
25
|
+
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
26
|
+
}
|
|
27
|
+
/** A page of the caller's jobs, newest first, with what their jobs may spend today. */
|
|
28
|
+
async function listJobs(options) {
|
|
29
|
+
const url = (0, url_formatter_1.formatURL)(jobsBase(options), query({ page: options.page, limit: options.limit, status: options.status }), { apiKey: options.apiKey, debug: options.debug });
|
|
30
|
+
return (0, api_request_1.requestAPI)({ url, action: "list jobs" });
|
|
31
|
+
}
|
|
32
|
+
/** One job with its plan, its journal and the questions of its nobody has answered. */
|
|
33
|
+
async function getJob(options) {
|
|
34
|
+
const url = (0, url_formatter_1.formatURL)(`${jobsBase(options)}/${encodeURIComponent(options.jobId)}`, {}, { apiKey: options.apiKey, debug: options.debug });
|
|
35
|
+
return (0, api_request_1.requestAPI)({ url, action: `read job ${options.jobId}` });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Stops a job.
|
|
39
|
+
*
|
|
40
|
+
* A job that had already finished is a 409: the call throws a `ButlerBotAPIError` whose
|
|
41
|
+
* `isConflict` is true and whose `body` still carries the job as it stands.
|
|
42
|
+
*/
|
|
43
|
+
async function cancelJob(options) {
|
|
44
|
+
const url = (0, url_formatter_1.formatURL)(`${jobsBase(options)}/${encodeURIComponent(options.jobId)}/cancel`, {}, { apiKey: options.apiKey, debug: options.debug });
|
|
45
|
+
return (0, api_request_1.requestAPI)({ url, method: "POST", action: `cancel job ${options.jobId}` });
|
|
46
|
+
}
|
|
47
|
+
/** Changes one job's autonomy: how far it may act, until when, and what it always asks about. */
|
|
48
|
+
async function updateJob(options) {
|
|
49
|
+
const url = (0, url_formatter_1.formatURL)(`${jobsBase(options)}/${encodeURIComponent(options.jobId)}`, {}, { apiKey: options.apiKey, debug: options.debug });
|
|
50
|
+
return (0, api_request_1.requestAPI)({
|
|
51
|
+
url,
|
|
52
|
+
method: "PATCH",
|
|
53
|
+
body: body({ autonomy: options.autonomy, autonomyUntil: options.autonomyUntil, alwaysAsk: options.alwaysAsk }),
|
|
54
|
+
action: `update job ${options.jobId}`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Changes the owner's job settings: their daily allowance and the autonomy new jobs start with. */
|
|
58
|
+
async function updateJobSettings(options) {
|
|
59
|
+
const endpoint = (options.serverURL || config_1.CONFIG.server) + (options.path || config_1.CONFIG.paths.jobs.settings);
|
|
60
|
+
const url = (0, url_formatter_1.formatURL)(endpoint, {}, { apiKey: options.apiKey, debug: options.debug });
|
|
61
|
+
return (0, api_request_1.requestAPI)({
|
|
62
|
+
url,
|
|
63
|
+
method: "PATCH",
|
|
64
|
+
body: body({
|
|
65
|
+
allowanceUsd: options.allowanceUsd,
|
|
66
|
+
allowancePercent: options.allowancePercent,
|
|
67
|
+
autonomy: options.autonomy,
|
|
68
|
+
alwaysAsk: options.alwaysAsk,
|
|
69
|
+
}),
|
|
70
|
+
action: "update job settings",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DeliveryAnswerResponse, DeliveryListResponse } from "../types/outreach";
|
|
2
|
+
/** What every outreach call needs: where the server is, and who is asking. */
|
|
3
|
+
export type OutreachRequestOptions = {
|
|
4
|
+
serverURL?: string;
|
|
5
|
+
/** The base path for outreach, when it is not the default. */
|
|
6
|
+
path?: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type ListDeliveriesOptions = OutreachRequestOptions & {
|
|
11
|
+
/** 1-based. The first page when it is not given. */
|
|
12
|
+
page?: number;
|
|
13
|
+
/** How many deliveries per page. */
|
|
14
|
+
limit?: number;
|
|
15
|
+
};
|
|
16
|
+
export type AnswerDeliveryOptions = OutreachRequestOptions & {
|
|
17
|
+
deliveryId: string;
|
|
18
|
+
/** The user's answer, in their own words. */
|
|
19
|
+
text: string;
|
|
20
|
+
/** On an approval, what they decided. */
|
|
21
|
+
decision?: "approve" | "deny";
|
|
22
|
+
};
|
|
23
|
+
/** A page of the inbox: what Alfred has told or asked this user outside a chat. */
|
|
24
|
+
export declare function listDeliveries(options: ListDeliveriesOptions): Promise<DeliveryListResponse>;
|
|
25
|
+
/**
|
|
26
|
+
* Answers one delivery, which closes it and wakes the job that asked.
|
|
27
|
+
*
|
|
28
|
+
* A delivery somebody has already answered is a 409: the call throws a `ButlerBotAPIError`
|
|
29
|
+
* whose `isConflict` is true and whose `body` still carries the delivery with the answer on it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function answerDelivery(options: AnswerDeliveryOptions): Promise<DeliveryAnswerResponse>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listDeliveries = listDeliveries;
|
|
4
|
+
exports.answerDelivery = answerDelivery;
|
|
5
|
+
const config_1 = require("../config");
|
|
6
|
+
const url_formatter_1 = require("../util/url_formatter");
|
|
7
|
+
const api_request_1 = require("./api_request");
|
|
8
|
+
function outreachBase(options) {
|
|
9
|
+
return (options.serverURL || config_1.CONFIG.server) + (options.path || config_1.CONFIG.paths.outreach.base);
|
|
10
|
+
}
|
|
11
|
+
/** Only what was actually given: `URLSearchParams` would otherwise send the word "undefined". */
|
|
12
|
+
function query(params) {
|
|
13
|
+
const given = {};
|
|
14
|
+
for (const [key, value] of Object.entries(params)) {
|
|
15
|
+
if (value !== undefined)
|
|
16
|
+
given[key] = String(value);
|
|
17
|
+
}
|
|
18
|
+
return given;
|
|
19
|
+
}
|
|
20
|
+
/** A page of the inbox: what Alfred has told or asked this user outside a chat. */
|
|
21
|
+
async function listDeliveries(options) {
|
|
22
|
+
const url = (0, url_formatter_1.formatURL)(outreachBase(options), query({ page: options.page, limit: options.limit }), { apiKey: options.apiKey, debug: options.debug });
|
|
23
|
+
return (0, api_request_1.requestAPI)({ url, action: "list deliveries" });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Answers one delivery, which closes it and wakes the job that asked.
|
|
27
|
+
*
|
|
28
|
+
* A delivery somebody has already answered is a 409: the call throws a `ButlerBotAPIError`
|
|
29
|
+
* whose `isConflict` is true and whose `body` still carries the delivery with the answer on it.
|
|
30
|
+
*/
|
|
31
|
+
async function answerDelivery(options) {
|
|
32
|
+
const url = (0, url_formatter_1.formatURL)(`${outreachBase(options)}/${encodeURIComponent(options.deliveryId)}/answer`, {}, { apiKey: options.apiKey, debug: options.debug });
|
|
33
|
+
return (0, api_request_1.requestAPI)({
|
|
34
|
+
url,
|
|
35
|
+
method: "POST",
|
|
36
|
+
body: options.decision === undefined ? { text: options.text } : { text: options.text, decision: options.decision },
|
|
37
|
+
action: `answer delivery ${options.deliveryId}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./job";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./job"), exports);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { Delivery } from "../outreach/delivery";
|
|
2
|
+
/**
|
|
3
|
+
* A job: long-running work Alfred does on its own, run as a sequence of short shifts.
|
|
4
|
+
*
|
|
5
|
+
* These are the shapes the jobs routes answer with. The server flattens a job row into a
|
|
6
|
+
* `JobView` before it leaves, and this is that view rather than the row.
|
|
7
|
+
*/
|
|
8
|
+
export declare const JOB_STATUSES: readonly ["queued", "running", "blocked", "waiting_user", "waiting_approval", "waiting_child", "waiting_budget", "review", "done", "failed", "cancelled"];
|
|
9
|
+
export type JobStatus = typeof JOB_STATUSES[number];
|
|
10
|
+
/** The statuses a job never leaves. */
|
|
11
|
+
export declare const TERMINAL_JOB_STATUSES: readonly JobStatus[];
|
|
12
|
+
export declare const JOB_AUTONOMY_MODES: readonly ["ask", "free", "auto"];
|
|
13
|
+
/** How far a job may act outward on its own: ask first, act freely, or act and report. */
|
|
14
|
+
export type JobAutonomy = typeof JOB_AUTONOMY_MODES[number];
|
|
15
|
+
/** One job as a listing or a detail read shows it. */
|
|
16
|
+
export type JobView = {
|
|
17
|
+
jobId: string;
|
|
18
|
+
/** A short name for listings, derived from the goal when nobody gave one. */
|
|
19
|
+
title: string;
|
|
20
|
+
/** The user's words, verbatim: what the job was asked to do. */
|
|
21
|
+
goal: string;
|
|
22
|
+
status: JobStatus;
|
|
23
|
+
/** Why it is where it is, specific enough to act on. */
|
|
24
|
+
statusDetail: string | null;
|
|
25
|
+
/** A one-line note the running shift may leave. */
|
|
26
|
+
progress: string | null;
|
|
27
|
+
/** The phase being worked, or the last one that was. */
|
|
28
|
+
currentPhaseId: string | null;
|
|
29
|
+
shiftsRun: number;
|
|
30
|
+
/** How many times the job has been reviewed. */
|
|
31
|
+
reviewRounds: number;
|
|
32
|
+
/** What the job has spent in total, in USD. */
|
|
33
|
+
spentUsd: number;
|
|
34
|
+
/** What the job has spent today, in USD. */
|
|
35
|
+
spendTodayUsd: number;
|
|
36
|
+
/** How many questions of this job's are waiting on the user. */
|
|
37
|
+
openQuestions: number;
|
|
38
|
+
lastRunAt: number;
|
|
39
|
+
/** The earliest the runner may pick it up again. */
|
|
40
|
+
wakeAt: number;
|
|
41
|
+
created: number;
|
|
42
|
+
/** `card://…/plan.md`, once a planning shift has written it. */
|
|
43
|
+
planCardUri: string | null;
|
|
44
|
+
/** `card://…/journal.md`, appended to as the job runs. */
|
|
45
|
+
journalCardUri: string | null;
|
|
46
|
+
/** The chat the job was asked for in, and where it reports back. */
|
|
47
|
+
originConversationId: string | null;
|
|
48
|
+
/** What was chosen for this job, or null when nothing was. */
|
|
49
|
+
autonomy: JobAutonomy | null;
|
|
50
|
+
/** When `autonomy` lapses back to asking, in UTC milliseconds. */
|
|
51
|
+
autonomyUntil: number | null;
|
|
52
|
+
/** Things this job asks about however free it otherwise is. */
|
|
53
|
+
alwaysAsk: string[];
|
|
54
|
+
/** What the gate actually reads, with the owner's setting and any lapse applied. */
|
|
55
|
+
effectiveAutonomy: JobAutonomy;
|
|
56
|
+
};
|
|
57
|
+
/** What the owner's jobs may spend today, and where that figure comes from. */
|
|
58
|
+
export type JobAllowanceGranted = {
|
|
59
|
+
perDayUsd: number;
|
|
60
|
+
/** What set the figure: the owner's own allowance, or their tier. */
|
|
61
|
+
source: string;
|
|
62
|
+
remainingUsd: number;
|
|
63
|
+
};
|
|
64
|
+
/** Why the owner's jobs may not run at all. */
|
|
65
|
+
export type JobAllowanceRefused = {
|
|
66
|
+
perDayUsd: 0;
|
|
67
|
+
source: null;
|
|
68
|
+
/** The reason in a word, such as a tier that does not include jobs. */
|
|
69
|
+
refused: string;
|
|
70
|
+
/** The same thing in a sentence, for showing a user. */
|
|
71
|
+
message: string;
|
|
72
|
+
};
|
|
73
|
+
export type JobAllowance = JobAllowanceGranted | JobAllowanceRefused;
|
|
74
|
+
/** Whether an allowance is a refusal rather than a grant. */
|
|
75
|
+
export declare function isJobAllowanceRefused(allowance: JobAllowance): allowance is JobAllowanceRefused;
|
|
76
|
+
export type JobPlanPhaseKind = "plan" | "work" | "review";
|
|
77
|
+
export type JobPlanPhaseStatus = "pending" | "running" | "blocked" | "done";
|
|
78
|
+
/** One phase of a job's plan, as the plan card spells it out. */
|
|
79
|
+
export type JobPlanPhase = {
|
|
80
|
+
id: string;
|
|
81
|
+
kind: JobPlanPhaseKind;
|
|
82
|
+
/** The model this phase is run on. */
|
|
83
|
+
model: string;
|
|
84
|
+
status: JobPlanPhaseStatus;
|
|
85
|
+
/** The card this phase has to leave behind before it may be done. */
|
|
86
|
+
artifact?: string;
|
|
87
|
+
/** What finishing it means, in the plan's own words. */
|
|
88
|
+
acceptance?: string;
|
|
89
|
+
/** Phase ids that have to be done first. */
|
|
90
|
+
dependsOn?: string[];
|
|
91
|
+
/** What the shift working this phase is told. */
|
|
92
|
+
brief?: string;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* What is wrong with a plan card that could not be read.
|
|
96
|
+
*
|
|
97
|
+
* The parse issue names the line and what is wrong with it; a plain string is what a server
|
|
98
|
+
* that only has a sentence about it sends.
|
|
99
|
+
*/
|
|
100
|
+
export type JobPlanIssue = string | {
|
|
101
|
+
line: number;
|
|
102
|
+
message: string;
|
|
103
|
+
};
|
|
104
|
+
export type JobPlanRead = {
|
|
105
|
+
ok: true;
|
|
106
|
+
raw: string;
|
|
107
|
+
phases: JobPlanPhase[];
|
|
108
|
+
} | {
|
|
109
|
+
ok: false;
|
|
110
|
+
reason: string;
|
|
111
|
+
issue?: JobPlanIssue;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* One line of a job's journal.
|
|
115
|
+
*
|
|
116
|
+
* Loosely typed on purpose: the journal is a card the model writes as well as the runtime, and
|
|
117
|
+
* a reader that dropped the keys it did not know would hide exactly the entries worth reading.
|
|
118
|
+
*/
|
|
119
|
+
export type JobJournalEntry = {
|
|
120
|
+
at: number;
|
|
121
|
+
kind?: string;
|
|
122
|
+
title?: string;
|
|
123
|
+
body?: string;
|
|
124
|
+
[key: string]: unknown;
|
|
125
|
+
};
|
|
126
|
+
/** What may be written to the owner's job settings. */
|
|
127
|
+
export type JobSettingsUpdate = {
|
|
128
|
+
/** A flat daily allowance, in USD. */
|
|
129
|
+
allowanceUsd?: number | null;
|
|
130
|
+
/** A daily allowance as a percentage of the owner's usage limit. */
|
|
131
|
+
allowancePercent?: number | null;
|
|
132
|
+
/** The autonomy new jobs start with. */
|
|
133
|
+
autonomy?: JobAutonomy | null;
|
|
134
|
+
/** Things every job of theirs asks about. */
|
|
135
|
+
alwaysAsk?: string[] | null;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* The owner's job settings as they now stand.
|
|
139
|
+
*
|
|
140
|
+
* Open on purpose: the settings pipeline owns this document, and a setting it grows is worth
|
|
141
|
+
* handing back rather than dropping because the SDK has not been rebuilt.
|
|
142
|
+
*/
|
|
143
|
+
export type JobSettings = JobSettingsUpdate & {
|
|
144
|
+
[key: string]: unknown;
|
|
145
|
+
};
|
|
146
|
+
export type JobListResponse = {
|
|
147
|
+
success: true;
|
|
148
|
+
jobs: JobView[];
|
|
149
|
+
page: number;
|
|
150
|
+
limit: number;
|
|
151
|
+
total: number;
|
|
152
|
+
/** The status the listing was filtered to, when it was. */
|
|
153
|
+
status?: JobStatus;
|
|
154
|
+
allowance: JobAllowance;
|
|
155
|
+
spentTodayUsd: number;
|
|
156
|
+
};
|
|
157
|
+
export type JobDetailResponse = {
|
|
158
|
+
success: true;
|
|
159
|
+
job: JobView;
|
|
160
|
+
/** The job's autonomy in a sentence, ready to show. */
|
|
161
|
+
autonomyLine: string;
|
|
162
|
+
plan: JobPlanRead;
|
|
163
|
+
journal: JobJournalEntry[];
|
|
164
|
+
/** Questions of this job's nobody has answered yet. */
|
|
165
|
+
openDeliveries: Delivery[];
|
|
166
|
+
};
|
|
167
|
+
export type JobCancelResponse = {
|
|
168
|
+
success: true;
|
|
169
|
+
job: JobView;
|
|
170
|
+
};
|
|
171
|
+
export type JobUpdateResponse = {
|
|
172
|
+
success: true;
|
|
173
|
+
job: JobView;
|
|
174
|
+
autonomyLine: string;
|
|
175
|
+
};
|
|
176
|
+
export type JobSettingsResponse = {
|
|
177
|
+
success: true;
|
|
178
|
+
jobs: JobSettings;
|
|
179
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JOB_AUTONOMY_MODES = exports.TERMINAL_JOB_STATUSES = exports.JOB_STATUSES = void 0;
|
|
4
|
+
exports.isJobAllowanceRefused = isJobAllowanceRefused;
|
|
5
|
+
/**
|
|
6
|
+
* A job: long-running work Alfred does on its own, run as a sequence of short shifts.
|
|
7
|
+
*
|
|
8
|
+
* These are the shapes the jobs routes answer with. The server flattens a job row into a
|
|
9
|
+
* `JobView` before it leaves, and this is that view rather than the row.
|
|
10
|
+
*/
|
|
11
|
+
exports.JOB_STATUSES = [
|
|
12
|
+
"queued",
|
|
13
|
+
"running",
|
|
14
|
+
"blocked",
|
|
15
|
+
"waiting_user",
|
|
16
|
+
"waiting_approval",
|
|
17
|
+
"waiting_child",
|
|
18
|
+
"waiting_budget",
|
|
19
|
+
"review",
|
|
20
|
+
"done",
|
|
21
|
+
"failed",
|
|
22
|
+
"cancelled",
|
|
23
|
+
];
|
|
24
|
+
/** The statuses a job never leaves. */
|
|
25
|
+
exports.TERMINAL_JOB_STATUSES = ["done", "failed", "cancelled"];
|
|
26
|
+
exports.JOB_AUTONOMY_MODES = ["ask", "free", "auto"];
|
|
27
|
+
/** Whether an allowance is a refusal rather than a grant. */
|
|
28
|
+
function isJobAllowanceRefused(allowance) {
|
|
29
|
+
return allowance.source === null;
|
|
30
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outreach: what Alfred has told or asked this user outside a chat, and their answers.
|
|
3
|
+
*
|
|
4
|
+
* A delivery is the record itself, so the inbox listing is the delivery rather than a
|
|
5
|
+
* notification about one. Answering closes it, and when it belongs to a job the answer is put
|
|
6
|
+
* on that job's inbox.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DELIVERY_INTENTS: readonly ["question", "update", "approval", "statement"];
|
|
9
|
+
export type DeliveryIntent = typeof DELIVERY_INTENTS[number];
|
|
10
|
+
/** The intents that leave something outstanding until the user answers. */
|
|
11
|
+
export declare const ANSWERABLE_DELIVERY_INTENTS: readonly DeliveryIntent[];
|
|
12
|
+
export declare const DELIVERY_SURFACES: readonly ["web", "discord"];
|
|
13
|
+
/** Where a delivery was put. */
|
|
14
|
+
export type DeliverySurfaceName = typeof DELIVERY_SURFACES[number];
|
|
15
|
+
export declare const DELIVERY_SURFACE_STATUSES: readonly ["pending", "sent", "failed"];
|
|
16
|
+
export type DeliverySurfaceStatus = typeof DELIVERY_SURFACE_STATUSES[number];
|
|
17
|
+
/** How one surface's send went. */
|
|
18
|
+
export type DeliverySurface = {
|
|
19
|
+
surface: DeliverySurfaceName;
|
|
20
|
+
status: DeliverySurfaceStatus;
|
|
21
|
+
/** Where on the surface it went: a Discord channel id, or "DM". */
|
|
22
|
+
channelId?: string | null;
|
|
23
|
+
/** What the channel called the message it sent. */
|
|
24
|
+
messageId?: string | null;
|
|
25
|
+
/** Why the last attempt failed. */
|
|
26
|
+
error?: string | null;
|
|
27
|
+
attempts: number;
|
|
28
|
+
/** When the last attempt was made, in UTC milliseconds. */
|
|
29
|
+
lastAt: number;
|
|
30
|
+
};
|
|
31
|
+
export declare const DELIVERY_ANSWER_VIA: readonly ["web", "discord", "chat", "tool"];
|
|
32
|
+
/** Which surface the answer came back on. */
|
|
33
|
+
export type DeliveryAnsweredVia = typeof DELIVERY_ANSWER_VIA[number];
|
|
34
|
+
export type DeliveryAnswer = {
|
|
35
|
+
at: number;
|
|
36
|
+
text: string;
|
|
37
|
+
via: DeliveryAnsweredVia;
|
|
38
|
+
/** On an approval, what the user decided. */
|
|
39
|
+
decision?: "approve" | "deny";
|
|
40
|
+
};
|
|
41
|
+
/** One delivery, as the inbox reads it. */
|
|
42
|
+
export type Delivery = {
|
|
43
|
+
deliveryId: string;
|
|
44
|
+
ownerUserId: string;
|
|
45
|
+
/** The chat this is about, so answering opens the conversation it belongs to. */
|
|
46
|
+
originConversationId: string | null;
|
|
47
|
+
/** The job this is about, when it is about one. */
|
|
48
|
+
jobId: string | null;
|
|
49
|
+
intent: DeliveryIntent;
|
|
50
|
+
message: string;
|
|
51
|
+
surfaces: DeliverySurface[];
|
|
52
|
+
/** Null until somebody answers. Only ever set once. */
|
|
53
|
+
answered: DeliveryAnswer | null;
|
|
54
|
+
created: number;
|
|
55
|
+
};
|
|
56
|
+
/** Whether a delivery is still waiting on the user. */
|
|
57
|
+
export declare function isDeliveryOpen(delivery: Delivery): boolean;
|
|
58
|
+
export type DeliveryListResponse = {
|
|
59
|
+
success: true;
|
|
60
|
+
deliveries: Delivery[];
|
|
61
|
+
page: number;
|
|
62
|
+
limit: number;
|
|
63
|
+
total: number;
|
|
64
|
+
};
|
|
65
|
+
/** What became of the answer once it was handed to the job that asked. */
|
|
66
|
+
export type DeliveryAnswerJobResult = {
|
|
67
|
+
jobId: string;
|
|
68
|
+
/** What the job did with it, or "dropped" when it could not take it. */
|
|
69
|
+
delivered: string;
|
|
70
|
+
/** Why it could not be delivered, when it could not. */
|
|
71
|
+
error?: string;
|
|
72
|
+
};
|
|
73
|
+
export type DeliveryAnswerResponse = {
|
|
74
|
+
success: true;
|
|
75
|
+
delivery: Delivery;
|
|
76
|
+
/** Present only when the delivery belonged to a job. */
|
|
77
|
+
job?: DeliveryAnswerJobResult;
|
|
78
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Outreach: what Alfred has told or asked this user outside a chat, and their answers.
|
|
4
|
+
*
|
|
5
|
+
* A delivery is the record itself, so the inbox listing is the delivery rather than a
|
|
6
|
+
* notification about one. Answering closes it, and when it belongs to a job the answer is put
|
|
7
|
+
* on that job's inbox.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.DELIVERY_ANSWER_VIA = exports.DELIVERY_SURFACE_STATUSES = exports.DELIVERY_SURFACES = exports.ANSWERABLE_DELIVERY_INTENTS = exports.DELIVERY_INTENTS = void 0;
|
|
11
|
+
exports.isDeliveryOpen = isDeliveryOpen;
|
|
12
|
+
exports.DELIVERY_INTENTS = ["question", "update", "approval", "statement"];
|
|
13
|
+
/** The intents that leave something outstanding until the user answers. */
|
|
14
|
+
exports.ANSWERABLE_DELIVERY_INTENTS = ["question", "approval"];
|
|
15
|
+
exports.DELIVERY_SURFACES = ["web", "discord"];
|
|
16
|
+
exports.DELIVERY_SURFACE_STATUSES = ["pending", "sent", "failed"];
|
|
17
|
+
exports.DELIVERY_ANSWER_VIA = ["web", "discord", "chat", "tool"];
|
|
18
|
+
/** Whether a delivery is still waiting on the user. */
|
|
19
|
+
function isDeliveryOpen(delivery) {
|
|
20
|
+
return exports.ANSWERABLE_DELIVERY_INTENTS.includes(delivery.intent) && !delivery.answered;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./delivery";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./delivery"), exports);
|
|
@@ -19,4 +19,6 @@ __exportStar(require("./response/v4"), exports);
|
|
|
19
19
|
__exportStar(require("./response/v5"), exports);
|
|
20
20
|
__exportStar(require("./state/convo_state_response"), exports);
|
|
21
21
|
__exportStar(require("./conversation/v4/conversation_v4"), exports);
|
|
22
|
+
__exportStar(require("./jobs"), exports);
|
|
23
|
+
__exportStar(require("./outreach"), exports);
|
|
22
24
|
__exportStar(require("./error"), exports);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an API call throws when the server did not answer with a success.
|
|
3
|
+
*
|
|
4
|
+
* One error class for every JSON route rather than a result type per method: the status is
|
|
5
|
+
* what a caller branches on, and it is carried here alongside whatever the server said, so
|
|
6
|
+
* "the job was already finished" (409) and "no job of yours has that id" (404) are told apart
|
|
7
|
+
* without parsing a message. `body` is the parsed response when there was one, so a 409 on a
|
|
8
|
+
* cancel still hands back the job, and a 409 on an answer still hands back the delivery.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ButlerBotAPIError extends Error {
|
|
11
|
+
/** The HTTP status the server answered with, or 0 when the call never got a response. */
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly statusText: string;
|
|
14
|
+
/** The server's short `error`, e.g. "Already finished". */
|
|
15
|
+
readonly error?: string;
|
|
16
|
+
/** The server's `errormessage`: the same thing in a sentence. */
|
|
17
|
+
readonly errormessage?: string;
|
|
18
|
+
/** The parsed response body, or the raw text when it was not JSON. */
|
|
19
|
+
readonly body: unknown;
|
|
20
|
+
constructor(options: {
|
|
21
|
+
action: string;
|
|
22
|
+
status: number;
|
|
23
|
+
statusText?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
errormessage?: string;
|
|
26
|
+
body?: unknown;
|
|
27
|
+
});
|
|
28
|
+
/** The resource is not there, or is not this key's. The two are answered alike on purpose. */
|
|
29
|
+
get isNotFound(): boolean;
|
|
30
|
+
/** Somebody got there first: the job had finished, or the delivery was already answered. */
|
|
31
|
+
get isConflict(): boolean;
|
|
32
|
+
/** The request itself was refused: a value the server would not take. */
|
|
33
|
+
get isBadRequest(): boolean;
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ButlerBotAPIError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* What an API call throws when the server did not answer with a success.
|
|
6
|
+
*
|
|
7
|
+
* One error class for every JSON route rather than a result type per method: the status is
|
|
8
|
+
* what a caller branches on, and it is carried here alongside whatever the server said, so
|
|
9
|
+
* "the job was already finished" (409) and "no job of yours has that id" (404) are told apart
|
|
10
|
+
* without parsing a message. `body` is the parsed response when there was one, so a 409 on a
|
|
11
|
+
* cancel still hands back the job, and a 409 on an answer still hands back the delivery.
|
|
12
|
+
*/
|
|
13
|
+
class ButlerBotAPIError extends Error {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
const detail = options.errormessage
|
|
16
|
+
|| options.error
|
|
17
|
+
// A body that was not JSON at all still says more than "Unknown error" does.
|
|
18
|
+
|| (typeof options.body === "string" && options.body ? options.body.slice(0, 500) : "Unknown error");
|
|
19
|
+
super(`Failed to ${options.action}: ${options.status} ${options.statusText || ""} - ${detail}`.replace(/\s+-/, " -"));
|
|
20
|
+
this.name = "ButlerBotAPIError";
|
|
21
|
+
this.status = options.status;
|
|
22
|
+
this.statusText = options.statusText || "";
|
|
23
|
+
this.error = options.error;
|
|
24
|
+
this.errormessage = options.errormessage;
|
|
25
|
+
this.body = options.body;
|
|
26
|
+
// `target: es2020` down-levels the subclass, which breaks `instanceof` without this.
|
|
27
|
+
Object.setPrototypeOf(this, ButlerBotAPIError.prototype);
|
|
28
|
+
}
|
|
29
|
+
/** The resource is not there, or is not this key's. The two are answered alike on purpose. */
|
|
30
|
+
get isNotFound() {
|
|
31
|
+
return this.status === 404;
|
|
32
|
+
}
|
|
33
|
+
/** Somebody got there first: the job had finished, or the delivery was already answered. */
|
|
34
|
+
get isConflict() {
|
|
35
|
+
return this.status === 409;
|
|
36
|
+
}
|
|
37
|
+
/** The request itself was refused: a value the server would not take. */
|
|
38
|
+
get isBadRequest() {
|
|
39
|
+
return this.status === 400;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.ButlerBotAPIError = ButlerBotAPIError;
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -322,6 +322,71 @@ seen. A conversation with nothing running simply ends the stream.
|
|
|
322
322
|
Neither transport can cancel a turn: `close()` stops delivery locally, and the reply is
|
|
323
323
|
still generated and stored.
|
|
324
324
|
|
|
325
|
+
## Jobs
|
|
326
|
+
|
|
327
|
+
A job is long-running work Alfred does on its own, run as a sequence of short shifts against
|
|
328
|
+
a plan it writes. The client reads them and steers them; it does not run them.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
const { jobs, allowance, spentTodayUsd } = await client.listJobs({ status: "running", page: 1, limit: 20 });
|
|
332
|
+
const { job, plan, journal, openDeliveries, autonomyLine } = await client.getJob({ jobId });
|
|
333
|
+
|
|
334
|
+
await client.updateJob({ jobId, autonomy: "free", autonomyUntil: Date.now() + 86_400_000 });
|
|
335
|
+
await client.cancelJob({ jobId });
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Paging is 1-based, and every listing answers with `page`, `limit` and `total`. A listing also
|
|
339
|
+
carries `allowance` — what this user's jobs may spend today and what is left of it, or
|
|
340
|
+
`{ perDayUsd: 0, source: null, refused, message }` when their plan does not include jobs.
|
|
341
|
+
|
|
342
|
+
`autonomy` is how far a job may act outward without asking: `ask`, `free` or `auto`.
|
|
343
|
+
`autonomyUntil` is when a loosened setting lapses back to asking, and `alwaysAsk` is what the
|
|
344
|
+
job asks about however free it otherwise is. `effectiveAutonomy` on a job is what the gate
|
|
345
|
+
actually reads, with the owner's default and any lapse already applied. What new jobs start
|
|
346
|
+
with, and what they may spend, is the user's own setting:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
await client.updateJobSettings({ allowanceUsd: 5, autonomy: "ask", alwaysAsk: ["spending money"] });
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Outreach
|
|
353
|
+
|
|
354
|
+
Outreach is what Alfred has told or asked this user outside a chat. The record is the inbox —
|
|
355
|
+
there is no separate notification — so a question a job is parked on is a delivery with
|
|
356
|
+
`intent: "question"` and no `answered`:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
const { deliveries } = await client.listDeliveries({ page: 1, limit: 20 });
|
|
360
|
+
const { delivery, job } = await client.answerDelivery({ deliveryId, text: "The one in Gardens" });
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Answering closes the delivery, and when it belongs to a job the answer is put on that job's
|
|
364
|
+
inbox, which wakes a job that was parked waiting for it — `job.delivered` says what became of
|
|
365
|
+
it. An approval takes `decision: "approve" | "deny"` alongside the text.
|
|
366
|
+
|
|
367
|
+
### When a call fails
|
|
368
|
+
|
|
369
|
+
Every jobs and outreach call throws `ButlerBotAPIError` when the server does not answer with a
|
|
370
|
+
success. The status is on the error, so the cases worth branching on are told apart without
|
|
371
|
+
reading a message, and the parsed body is kept — a rejected cancel still carries the job, a
|
|
372
|
+
rejected answer still carries the delivery:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { ButlerBotAPIError } from "@butlerbot/sdk";
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
await client.cancelJob({ jobId });
|
|
379
|
+
} catch (error) {
|
|
380
|
+
if (error instanceof ButlerBotAPIError && error.isConflict) {
|
|
381
|
+
// 409: it had already finished. `error.body.job` is how it settled.
|
|
382
|
+
} else throw error;
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`isNotFound` (404), `isConflict` (409) and `isBadRequest` (400) are the three; `status`,
|
|
387
|
+
`error` and `errormessage` are the server's own words. A job or delivery that is not this key's
|
|
388
|
+
is answered exactly as one that does not exist, so a 404 means either.
|
|
389
|
+
|
|
325
390
|
## Environment
|
|
326
391
|
|
|
327
392
|
Node 18+. Node 22 and every browser have a built-in WebSocket; on older Node, install
|