totemlayer 0.1.25 → 0.1.30
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 +36 -30
- package/bin/mcp.mjs +236 -45
- package/bin/network-runtime.mjs +2 -0
- package/bin/sdk/action-types.js +0 -1
- package/bin/sdk/actions.js +1 -84
- package/bin/sdk/errors.js +1 -67
- package/bin/sdk/index.d.ts +34 -1
- package/bin/sdk/index.js +1 -433
- package/bin/sdk/inspector.js +1 -323
- package/bin/sdk/request-options.js +1 -36
- package/bin/sdk/transport.js +1 -202
- package/bin/sdk/types.d.ts +56 -1
- package/bin/sdk/types.js +0 -1
- package/bin/totem.mjs +2 -2
- package/package.json +1 -1
- package/scripts/prepare.mjs +16 -1
package/bin/sdk/errors.js
CHANGED
|
@@ -1,67 +1 @@
|
|
|
1
|
-
|
|
2
|
-
code;
|
|
3
|
-
status;
|
|
4
|
-
requestId;
|
|
5
|
-
retryAfterSeconds;
|
|
6
|
-
constructor(message, options) {
|
|
7
|
-
super(message, { cause: options.cause });
|
|
8
|
-
this.name = new.target.name;
|
|
9
|
-
this.code = options.code;
|
|
10
|
-
this.status = options.status;
|
|
11
|
-
this.requestId = options.requestId;
|
|
12
|
-
this.retryAfterSeconds = options.retryAfterSeconds;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
export class TotemAuthenticationError extends TotemError {
|
|
16
|
-
}
|
|
17
|
-
export class TotemPermissionError extends TotemError {
|
|
18
|
-
}
|
|
19
|
-
export class TotemNotFoundError extends TotemError {
|
|
20
|
-
}
|
|
21
|
-
export class TotemConflictError extends TotemError {
|
|
22
|
-
}
|
|
23
|
-
export class TotemRateLimitError extends TotemError {
|
|
24
|
-
}
|
|
25
|
-
export class TotemTimeoutError extends TotemError {
|
|
26
|
-
}
|
|
27
|
-
export class TotemNetworkError extends TotemError {
|
|
28
|
-
}
|
|
29
|
-
export class TotemCanceledError extends TotemError {
|
|
30
|
-
}
|
|
31
|
-
export function apiError(status, body, requestId) {
|
|
32
|
-
const error = body &&
|
|
33
|
-
typeof body === "object" &&
|
|
34
|
-
"error" in body &&
|
|
35
|
-
body.error &&
|
|
36
|
-
typeof body.error === "object"
|
|
37
|
-
? body.error
|
|
38
|
-
: {};
|
|
39
|
-
const message = "message" in error && typeof error.message === "string"
|
|
40
|
-
? error.message
|
|
41
|
-
: `Totem request failed (${status}).`;
|
|
42
|
-
const code = "code" in error && typeof error.code === "string" ? error.code : "HTTP_ERROR";
|
|
43
|
-
const id = "requestId" in error && typeof error.requestId === "string" ? error.requestId : requestId;
|
|
44
|
-
const Constructor = status === 401
|
|
45
|
-
? TotemAuthenticationError
|
|
46
|
-
: status === 403
|
|
47
|
-
? TotemPermissionError
|
|
48
|
-
: status === 404
|
|
49
|
-
? TotemNotFoundError
|
|
50
|
-
: status === 409
|
|
51
|
-
? TotemConflictError
|
|
52
|
-
: status === 429
|
|
53
|
-
? TotemRateLimitError
|
|
54
|
-
: TotemError;
|
|
55
|
-
const details = "details" in error ? error.details : undefined;
|
|
56
|
-
const retryAfter = details && typeof details === "object" && "retryAfterSeconds" in details
|
|
57
|
-
? details.retryAfterSeconds
|
|
58
|
-
: undefined;
|
|
59
|
-
const retryAfterSeconds = status === 429 &&
|
|
60
|
-
typeof retryAfter === "number" &&
|
|
61
|
-
Number.isSafeInteger(retryAfter) &&
|
|
62
|
-
retryAfter > 0 &&
|
|
63
|
-
retryAfter <= 86400
|
|
64
|
-
? retryAfter
|
|
65
|
-
: undefined;
|
|
66
|
-
return new Constructor(message, { code, status, requestId: id, retryAfterSeconds });
|
|
67
|
-
}
|
|
1
|
+
class TotemError extends Error{code;status;requestId;retryAfterSeconds;constructor(message,options){super(message,{cause:options.cause});this.name=new.target.name;this.code=options.code;this.status=options.status;this.requestId=options.requestId;this.retryAfterSeconds=options.retryAfterSeconds}}class TotemAuthenticationError extends TotemError{}class TotemPermissionError extends TotemError{}class TotemNotFoundError extends TotemError{}class TotemConflictError extends TotemError{}class TotemRateLimitError extends TotemError{}class TotemTimeoutError extends TotemError{}class TotemNetworkError extends TotemError{}class TotemCanceledError extends TotemError{}function apiError(status,body,requestId){const error=body&&typeof body==="object"&&"error"in body&&body.error&&typeof body.error==="object"?body.error:{};const message="message"in error&&typeof error.message==="string"?error.message:`Totem request failed (${status}).`;const code="code"in error&&typeof error.code==="string"?error.code:"HTTP_ERROR";const id="requestId"in error&&typeof error.requestId==="string"?error.requestId:requestId;const Constructor=status===401?TotemAuthenticationError:status===403?TotemPermissionError:status===404?TotemNotFoundError:status===409?TotemConflictError:status===429?TotemRateLimitError:TotemError;const details="details"in error?error.details:void 0;const retryAfter=details&&typeof details==="object"&&"retryAfterSeconds"in details?details.retryAfterSeconds:void 0;const retryAfterSeconds=status===429&&typeof retryAfter==="number"&&Number.isSafeInteger(retryAfter)&&retryAfter>0&&retryAfter<=86400?retryAfter:void 0;return new Constructor(message,{code,status,requestId:id,retryAfterSeconds})}export{TotemAuthenticationError,TotemCanceledError,TotemConflictError,TotemError,TotemNetworkError,TotemNotFoundError,TotemPermissionError,TotemRateLimitError,TotemTimeoutError,apiError};
|
package/bin/sdk/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createLocalInspector, InspectionError } from "./inspector.js";
|
|
2
2
|
import type { ActionProposal, AssistantActionCheck, AssistantActionCheckList, AssistantActionReceiptResult, AssistantCheckOptions } from "./action-types.js";
|
|
3
|
-
import type { MissionCoordinationUpdate, MissionSessionReport, Agent, AssistantContext, AssistantMissionPermissions, Attachment, Chat, ChatCommand, ChatDetail, ChatEvent, ChatRun, RuntimeHeartbeat, Conversation, ListenOptions, ListMessages, ListMissions, ListTasks, Message, MessageContent, MessageHandler, Metadata, MissionArtifact, MissionDetail, MissionEvent, MissionEventInput, MissionSummary, MissionWorkItem, Page, Pagination, RequestOptions, RepoClaim, RepoClaimState, RepositoryCoordination, SearchAgents, SendMessage, Subscription, Task, TaskState, TotemOptions, WriteOptions } from "./types.js";
|
|
3
|
+
import type { MissionCoordinationUpdate, MissionSessionReport, Agent, AssistantContext, AssistantMissionPermissions, Attachment, Chat, ChatCommand, ChatDetail, ChatEvent, ChatRun, RuntimeHeartbeat, Conversation, ListenOptions, ListMessages, ListMissions, ListTasks, Message, MessageContent, MessageHandler, Metadata, MissionArtifact, MissionDetail, MissionEvent, MissionEventInput, MissionSummary, MissionWorkItem, Page, Pagination, RequestOptions, RepoClaim, RepoClaimState, RepositoryCoordination, SearchAgents, SendMessage, Subscription, Task, TaskState, TotemOptions, WriteOptions, NetworkActivityPage, NetworkActionInput, NetworkActionRequest, NetworkProtectedAction } from "./types.js";
|
|
4
4
|
export * from "./types.js";
|
|
5
5
|
export * from "./errors.js";
|
|
6
6
|
export * from "./actions.js";
|
|
@@ -11,6 +11,38 @@ export declare class Totem {
|
|
|
11
11
|
static connect(options: TotemOptions): Promise<ConnectedAgent>;
|
|
12
12
|
connect(): Promise<ConnectedAgent>;
|
|
13
13
|
agent(options?: RequestOptions): Promise<Agent>;
|
|
14
|
+
activity: {
|
|
15
|
+
list: (query?: { cursor?: string; limit?: number }, options?: RequestOptions) => Promise<NetworkActivityPage>;
|
|
16
|
+
};
|
|
17
|
+
/** Native Network authorization. Human approval remains in the receiving owner's conversation. */
|
|
18
|
+
actions: {
|
|
19
|
+
list: (query?: {
|
|
20
|
+
state?: Exclude<NetworkActionRequest["state"], "expired">;
|
|
21
|
+
direction?: "inbound" | "outbound";
|
|
22
|
+
cursor?: string;
|
|
23
|
+
limit?: number;
|
|
24
|
+
}, options?: RequestOptions) => Promise<Page<NetworkActionRequest>>;
|
|
25
|
+
request: (input: NetworkActionInput, options?: WriteOptions) => Promise<NetworkActionRequest>;
|
|
26
|
+
get: (id: string, options?: RequestOptions) => Promise<NetworkActionRequest>;
|
|
27
|
+
dispatch: (id: string, input: {
|
|
28
|
+
digest: string;
|
|
29
|
+
action: NetworkProtectedAction;
|
|
30
|
+
}, options?: RequestOptions) => Promise<NetworkActionRequest>;
|
|
31
|
+
complete: (id: string, input: {
|
|
32
|
+
executionId: string;
|
|
33
|
+
outcome: "succeeded" | "failed" | "uncertain";
|
|
34
|
+
summary: string;
|
|
35
|
+
}, options?: RequestOptions) => Promise<NetworkActionRequest>;
|
|
36
|
+
cancel: (id: string, options?: RequestOptions) => Promise<NetworkActionRequest>;
|
|
37
|
+
/** Execute one registered local operation after the exact receiver approval is consumed. */
|
|
38
|
+
run: <T>(id: string, action: NetworkProtectedAction, execute: (approved: NetworkProtectedAction, executionId: string) => Promise<{
|
|
39
|
+
value: T;
|
|
40
|
+
summary: string;
|
|
41
|
+
}>, options?: RequestOptions) => Promise<{
|
|
42
|
+
value: T;
|
|
43
|
+
receipt: NetworkActionRequest;
|
|
44
|
+
}>;
|
|
45
|
+
};
|
|
14
46
|
assistant: {
|
|
15
47
|
context: (options?: RequestOptions) => Promise<AssistantContext>;
|
|
16
48
|
checks: {
|
|
@@ -235,6 +267,7 @@ export declare class Totem {
|
|
|
235
267
|
postUpdate: (id: string, text: string, options?: WriteOptions) => Promise<Message>;
|
|
236
268
|
update: (id: string, input: {
|
|
237
269
|
state: Exclude<TaskState, "submitted" | "canceled">;
|
|
270
|
+
statusMessage?: NonNullable<Task["statusMessage"]>;
|
|
238
271
|
artifacts?: Task["artifacts"];
|
|
239
272
|
expectedVersion?: number;
|
|
240
273
|
}, options?: RequestOptions) => Promise<Task>;
|
package/bin/sdk/index.js
CHANGED
|
@@ -1,433 +1 @@
|
|
|
1
|
-
import { TotemError } from "./errors.js";
|
|
2
|
-
import { createLocalInspector, InspectionError } from "./inspector.js";
|
|
3
|
-
import { idempotencyKey, sleep, Transport } from "./transport.js";
|
|
4
|
-
import { assertInputFields, assistantCheckOptions, signalOnlyOptions } from "./request-options.js";
|
|
5
|
-
export * from "./types.js";
|
|
6
|
-
export * from "./errors.js";
|
|
7
|
-
export * from "./actions.js";
|
|
8
|
-
export { idempotencyKey } from "./transport.js";
|
|
9
|
-
function content(input) {
|
|
10
|
-
// Runtime callers (including JavaScript) cannot accidentally provide a sender.
|
|
11
|
-
if ("from" in input)
|
|
12
|
-
throw new TotemError("Sender identity comes from the API key.", { code: "INVALID_MESSAGE" });
|
|
13
|
-
return input.content ?? { type: "text", text: input.text ?? input.message };
|
|
14
|
-
}
|
|
15
|
-
async function* paginate(fetchPage, cursor) {
|
|
16
|
-
const seen = new Set();
|
|
17
|
-
if (cursor)
|
|
18
|
-
seen.add(cursor);
|
|
19
|
-
do {
|
|
20
|
-
const page = await fetchPage(cursor);
|
|
21
|
-
for (const item of page.data)
|
|
22
|
-
yield item;
|
|
23
|
-
cursor = page.nextCursor ?? undefined;
|
|
24
|
-
if (cursor && seen.has(cursor))
|
|
25
|
-
throw new TotemError("Totem returned a repeated pagination cursor.", {
|
|
26
|
-
code: "INVALID_RESPONSE",
|
|
27
|
-
});
|
|
28
|
-
if (cursor)
|
|
29
|
-
seen.add(cursor);
|
|
30
|
-
} while (cursor);
|
|
31
|
-
}
|
|
32
|
-
export class Totem {
|
|
33
|
-
#transport;
|
|
34
|
-
#address;
|
|
35
|
-
constructor(options) {
|
|
36
|
-
this.#transport = new Transport(options);
|
|
37
|
-
this.#address = options.address?.trim().toLowerCase();
|
|
38
|
-
}
|
|
39
|
-
static async connect(options) {
|
|
40
|
-
return new Totem(options).connect();
|
|
41
|
-
}
|
|
42
|
-
async connect() {
|
|
43
|
-
const identity = await this.agent();
|
|
44
|
-
if (this.#address && this.#address !== identity.address.toLowerCase()) {
|
|
45
|
-
throw new TotemError("The configured address does not match the authenticated agent.", {
|
|
46
|
-
code: "IDENTITY_MISMATCH",
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
return new ConnectedAgent(this, identity);
|
|
50
|
-
}
|
|
51
|
-
async agent(options) {
|
|
52
|
-
return (await this.#transport.request("GET", "/agents/me", options)).data;
|
|
53
|
-
}
|
|
54
|
-
assistant = {
|
|
55
|
-
context: async (options) => (await this.#transport.request("GET", "/assistant/context", signalOnlyOptions(options))).data,
|
|
56
|
-
checks: {
|
|
57
|
-
create: async (missionId, input, options) => {
|
|
58
|
-
assertInputFields(input, ["workItemId", "leaseToken", "action"]);
|
|
59
|
-
return (await this.#transport.request("POST", `/assistant/missions/${encodeURIComponent(missionId)}/action-checks`, { ...assistantCheckOptions(options), body: input })).data;
|
|
60
|
-
},
|
|
61
|
-
list: async (missionId, workItemId, options) => (await this.#transport.request("GET", `/assistant/missions/${encodeURIComponent(missionId)}/action-checks`, { ...signalOnlyOptions(options), query: { workItemId } })).data,
|
|
62
|
-
recordReceipt: async (missionId, suiteId, input, options) => {
|
|
63
|
-
assertInputFields(input, ["workItemId"]);
|
|
64
|
-
return (await this.#transport.request("POST", `/assistant/missions/${encodeURIComponent(missionId)}/action-checks/${encodeURIComponent(suiteId)}/receipt`, { ...signalOnlyOptions(options), body: input })).data;
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
permissions: async (missionId, options) => (await this.#transport.request("GET", `/assistant/missions/${encodeURIComponent(missionId)}/permissions`, signalOnlyOptions(options))).data,
|
|
68
|
-
};
|
|
69
|
-
async resolve(address, options) {
|
|
70
|
-
return (await this.#transport.request("GET", `/resolve/${encodeURIComponent(address)}`, options)).data;
|
|
71
|
-
}
|
|
72
|
-
agents = {
|
|
73
|
-
search: (query = {}, options) => this.#transport.request("GET", "/agents/search", { ...options, query }),
|
|
74
|
-
iterate: (query = {}, options) => paginate((cursor) => this.agents.search({ ...query, cursor }, options), query.cursor),
|
|
75
|
-
};
|
|
76
|
-
attachments = {
|
|
77
|
-
upload: async (name, mediaType, bytes, options = {}) => {
|
|
78
|
-
if (!bytes.byteLength || bytes.byteLength > 2 * 1024 * 1024)
|
|
79
|
-
throw new TotemError("Attachments must be nonempty and no larger than 2 MiB.", {
|
|
80
|
-
code: "ATTACHMENT_SIZE_INVALID",
|
|
81
|
-
});
|
|
82
|
-
return (await this.#transport.request("POST", "/attachments", {
|
|
83
|
-
...options,
|
|
84
|
-
binaryBody: bytes,
|
|
85
|
-
headers: { "Content-Type": mediaType, "X-Totem-File-Name": encodeURIComponent(name) },
|
|
86
|
-
})).data;
|
|
87
|
-
},
|
|
88
|
-
download: (id, options) => this.#transport.download(`/attachments/${encodeURIComponent(id)}`, options),
|
|
89
|
-
};
|
|
90
|
-
messages = {
|
|
91
|
-
send: async (input, options = {}) => {
|
|
92
|
-
const body = { ...input, content: content(input) };
|
|
93
|
-
delete body.text;
|
|
94
|
-
delete body.message;
|
|
95
|
-
return (await this.#transport.request("POST", "/messages", {
|
|
96
|
-
...options,
|
|
97
|
-
body,
|
|
98
|
-
idempotencyKey: options.idempotencyKey ?? idempotencyKey(),
|
|
99
|
-
})).data;
|
|
100
|
-
},
|
|
101
|
-
list: (query = {}, options) => this.#transport.request("GET", "/messages", { ...options, query }),
|
|
102
|
-
iterate: (query = {}, options) => paginate((cursor) => this.messages.list({ ...query, cursor }, options), query.cursor),
|
|
103
|
-
get: async (id, options) => (await this.#transport.request("GET", `/messages/${encodeURIComponent(id)}`, options)).data,
|
|
104
|
-
reply: async (id, text, options = {}) => (await this.#transport.request("POST", `/messages/${encodeURIComponent(id)}/reply`, {
|
|
105
|
-
...options,
|
|
106
|
-
body: {
|
|
107
|
-
content: {
|
|
108
|
-
type: "text",
|
|
109
|
-
text,
|
|
110
|
-
...(options.attachments ? { attachments: options.attachments } : {}),
|
|
111
|
-
},
|
|
112
|
-
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
113
|
-
},
|
|
114
|
-
idempotencyKey: options.idempotencyKey ?? `sdk:reply:${id}`,
|
|
115
|
-
})).data,
|
|
116
|
-
replyContent: async (id, content, options = {}) => (await this.#transport.request("POST", `/messages/${encodeURIComponent(id)}/reply`, {
|
|
117
|
-
...options,
|
|
118
|
-
body: { content },
|
|
119
|
-
idempotencyKey: options.idempotencyKey ?? idempotencyKey(),
|
|
120
|
-
})).data,
|
|
121
|
-
ack: async (id, options) => (await this.#transport.request("POST", `/messages/${encodeURIComponent(id)}/ack`, {
|
|
122
|
-
...options,
|
|
123
|
-
body: {},
|
|
124
|
-
retrySafe: true,
|
|
125
|
-
})).data,
|
|
126
|
-
};
|
|
127
|
-
send(input, textOrOptions, options) {
|
|
128
|
-
if (typeof input === "string")
|
|
129
|
-
return this.messages.send({ to: input, text: textOrOptions }, options);
|
|
130
|
-
return this.messages.send(input, textOrOptions);
|
|
131
|
-
}
|
|
132
|
-
conversations = {
|
|
133
|
-
create: async (input, options) => {
|
|
134
|
-
// Creation has no automatic retry until the server supports an idempotent contract.
|
|
135
|
-
const result = await this.#transport.request("POST", "/conversations", { ...options, body: input });
|
|
136
|
-
return new ConversationHandle(this, result.data, input.with);
|
|
137
|
-
},
|
|
138
|
-
list: (query = {}, options) => this.#transport.request("GET", "/conversations", { ...options, query }),
|
|
139
|
-
get: async (id, options) => (await this.#transport.request("GET", `/conversations/${encodeURIComponent(id)}`, options)).data,
|
|
140
|
-
messages: (id, query = {}, options) => this.#transport.request("GET", `/conversations/${encodeURIComponent(id)}/messages`, {
|
|
141
|
-
...options,
|
|
142
|
-
query,
|
|
143
|
-
}),
|
|
144
|
-
};
|
|
145
|
-
startConversation(input, options) {
|
|
146
|
-
return this.conversations.create(input, options);
|
|
147
|
-
}
|
|
148
|
-
missions = {
|
|
149
|
-
list: async (options) => (await this.#transport.request("GET", "/missions", options)).data.missions,
|
|
150
|
-
listPage: async (query = {}, options) => {
|
|
151
|
-
const page = await this.#transport.request("GET", "/missions", { ...options, query });
|
|
152
|
-
return { data: page.data.missions, nextCursor: page.nextCursor ?? null };
|
|
153
|
-
},
|
|
154
|
-
iterate: (query = {}, options) => paginate((cursor) => this.missions.listPage({ ...query, cursor }, options), query.cursor),
|
|
155
|
-
get: async (id, options) => {
|
|
156
|
-
const result = await this.#transport.request("GET", `/missions/${encodeURIComponent(id)}`, options);
|
|
157
|
-
return {
|
|
158
|
-
...result.data.mission,
|
|
159
|
-
members: result.data.members,
|
|
160
|
-
workItems: result.data.workItems,
|
|
161
|
-
artifacts: result.data.artifacts,
|
|
162
|
-
events: result.data.events,
|
|
163
|
-
budget: result.data.budget,
|
|
164
|
-
coordination: result.data.coordination,
|
|
165
|
-
};
|
|
166
|
-
},
|
|
167
|
-
coordination: async (id, version, options) => (await this.#transport.request("GET", `/missions/${encodeURIComponent(id)}/coordination`, {
|
|
168
|
-
...options,
|
|
169
|
-
query: { version },
|
|
170
|
-
})).data,
|
|
171
|
-
sessions: {
|
|
172
|
-
report: async (id, input, options) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/sessions`, {
|
|
173
|
-
...options,
|
|
174
|
-
body: input,
|
|
175
|
-
retrySafe: true,
|
|
176
|
-
})).data,
|
|
177
|
-
},
|
|
178
|
-
workItems: async (id, options) => (await this.#transport.request("GET", `/missions/${encodeURIComponent(id)}/work-items`, options)).data.workItems,
|
|
179
|
-
/** Empty data can still have a nextCursor: readiness is checked after scanning candidates. */
|
|
180
|
-
workItemsPage: async (id, query = {}, options) => {
|
|
181
|
-
const page = await this.#transport.request("GET", `/missions/${encodeURIComponent(id)}/work-items`, { ...options, query });
|
|
182
|
-
return { data: page.data.workItems, nextCursor: page.nextCursor ?? null };
|
|
183
|
-
},
|
|
184
|
-
iterateWorkItems: (id, query = {}, options) => paginate((cursor) => this.missions.workItemsPage(id, { ...query, cursor }, options), query.cursor),
|
|
185
|
-
assign: async (id, input, options = {}) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/work-items`, {
|
|
186
|
-
...options,
|
|
187
|
-
body: input,
|
|
188
|
-
idempotencyKey: options.idempotencyKey ?? idempotencyKey(),
|
|
189
|
-
})).data,
|
|
190
|
-
claim: async (id, workItemId, options) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/claim`, { ...options, body: {} })).data,
|
|
191
|
-
renew: async (id, workItemId, leaseToken, options) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/renew`, { ...options, body: { leaseToken }, retrySafe: true })).data,
|
|
192
|
-
complete: async (id, workItemId, input, options = {}) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/complete`, { ...options, body: input, idempotencyKey: options.idempotencyKey ?? idempotencyKey() })).data,
|
|
193
|
-
reserveEffect: async (id, input, options = {}) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/effects`, {
|
|
194
|
-
...options,
|
|
195
|
-
body: input,
|
|
196
|
-
idempotencyKey: options.idempotencyKey ?? idempotencyKey(),
|
|
197
|
-
})).data,
|
|
198
|
-
listEffects: async (id, workItemId, options) => (await this.#transport.request("GET", `/missions/${encodeURIComponent(id)}/effects`, {
|
|
199
|
-
...options,
|
|
200
|
-
query: { workItemId },
|
|
201
|
-
})).data.effects,
|
|
202
|
-
dispatchEffect: async (id, effectId, leaseToken, options) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/effects/${encodeURIComponent(effectId)}/dispatch`, {
|
|
203
|
-
...options,
|
|
204
|
-
body: { leaseToken },
|
|
205
|
-
})).data,
|
|
206
|
-
settleEffect: async (id, effectId, input, options) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/effects/${encodeURIComponent(effectId)}/settle`, { ...options, body: input, retrySafe: true })).data,
|
|
207
|
-
events: {
|
|
208
|
-
list: (id, query = {}, options) => this.#transport.request("GET", `/missions/${encodeURIComponent(id)}/events`, {
|
|
209
|
-
...options,
|
|
210
|
-
query,
|
|
211
|
-
}),
|
|
212
|
-
iterate: (id, query = {}, options) => paginate((cursor) => this.missions.events.list(id, { ...query, cursor }, options), query.cursor),
|
|
213
|
-
post: async (id, input, options = {}) => {
|
|
214
|
-
if ("from" in input || "sender" in input || "actor" in input)
|
|
215
|
-
throw new TotemError("Mission event actors come from the agent credential.", {
|
|
216
|
-
code: "INVALID_EVENT",
|
|
217
|
-
});
|
|
218
|
-
return (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/events`, { ...options, body: input, idempotencyKey: options.idempotencyKey ?? idempotencyKey() })).data;
|
|
219
|
-
},
|
|
220
|
-
ack: async (id, eventId, options) => (await this.#transport.request("POST", `/missions/${encodeURIComponent(id)}/events/${encodeURIComponent(eventId)}/ack`, { ...options, body: {}, retrySafe: true })).data,
|
|
221
|
-
},
|
|
222
|
-
};
|
|
223
|
-
chats = {
|
|
224
|
-
list: (query = {}, options) => this.#transport.request("GET", "/chats", { ...options, query }),
|
|
225
|
-
get: async (id, options = {}) => (await this.#transport.request("GET", `/chats/${encodeURIComponent(id)}`, {
|
|
226
|
-
signal: options.signal,
|
|
227
|
-
query: { cursor: options.cursor, before: options.before, limit: options.limit },
|
|
228
|
-
})).data,
|
|
229
|
-
/** Publish as the authenticated agent; this never creates a human command. */
|
|
230
|
-
updates: async (id, text, options = {}) => (await this.#transport.request("POST", `/chats/${encodeURIComponent(id)}/updates`, {
|
|
231
|
-
...options,
|
|
232
|
-
body: { text },
|
|
233
|
-
idempotencyKey: options.idempotencyKey ?? idempotencyKey(),
|
|
234
|
-
})).data,
|
|
235
|
-
};
|
|
236
|
-
chatCommands = {
|
|
237
|
-
list: (query = {}, options) => this.#transport.request("GET", "/chat-commands", { ...options, query }),
|
|
238
|
-
claim: async (id, options) => (await this.#transport.request("POST", `/chat-commands/${encodeURIComponent(id)}/claim`, { ...options, body: {} })).data,
|
|
239
|
-
renew: async (id, leaseToken, options) => (await this.#transport.request("POST", `/chat-commands/${encodeURIComponent(id)}/renew`, { ...options, body: { leaseToken }, retrySafe: true })).data,
|
|
240
|
-
complete: async (id, input, options = {}) => (await this.#transport.request("POST", `/chat-commands/${encodeURIComponent(id)}/complete`, { ...options, body: input, idempotencyKey: options.idempotencyKey ?? idempotencyKey() })).data,
|
|
241
|
-
fail: async (id, input, options) => (await this.#transport.request("POST", `/chat-commands/${encodeURIComponent(id)}/fail`, { ...options, body: input, retrySafe: true })).data,
|
|
242
|
-
};
|
|
243
|
-
runtime = {
|
|
244
|
-
activations: async (options) => (await this.#transport.request("GET", "/runtime/activations", options)).data,
|
|
245
|
-
reportActivation: async (input, options) => (await this.#transport.request("POST", "/runtime/activations", { ...options, body: input, retrySafe: true })).data,
|
|
246
|
-
start: async (input, options) => (await this.#transport.request("POST", "/runtime/start", { ...options, body: input, retrySafe: true })).data,
|
|
247
|
-
probe: async (runId, options) => (await this.#transport.request("GET", "/runtime/probe", { ...options, query: { runId } })).data,
|
|
248
|
-
replyProbe: async (input, options) => (await this.#transport.request("POST", "/runtime/probe", { ...options, body: input, retrySafe: true })).data,
|
|
249
|
-
heartbeat: async (input = {}, options) => (await this.#transport.request("POST", "/runtime/heartbeat", {
|
|
250
|
-
...options,
|
|
251
|
-
body: input,
|
|
252
|
-
retrySafe: true,
|
|
253
|
-
})).data,
|
|
254
|
-
};
|
|
255
|
-
tasks = {
|
|
256
|
-
create: async (input, options = {}) => (await this.#transport.request("POST", "/tasks", {
|
|
257
|
-
...options,
|
|
258
|
-
body: {
|
|
259
|
-
to: input.to,
|
|
260
|
-
content: content(input),
|
|
261
|
-
conversationId: input.conversationId,
|
|
262
|
-
metadata: input.metadata,
|
|
263
|
-
ttlSeconds: input.ttlSeconds,
|
|
264
|
-
authorization: input.authorization,
|
|
265
|
-
},
|
|
266
|
-
idempotencyKey: options.idempotencyKey ?? idempotencyKey(),
|
|
267
|
-
})).data,
|
|
268
|
-
get: async (id, options) => (await this.#transport.request("GET", `/tasks/${encodeURIComponent(id)}`, options)).data,
|
|
269
|
-
list: (query = {}, options) => this.#transport.request("GET", "/tasks", { ...options, query }),
|
|
270
|
-
iterate: (query = {}, options) => paginate((cursor) => this.tasks.list({ ...query, cursor }, options), query.cursor),
|
|
271
|
-
postUpdate: async (id, text, options = {}) => (await this.#transport.request("POST", `/tasks/${encodeURIComponent(id)}/messages`, {
|
|
272
|
-
...options,
|
|
273
|
-
body: { content: { type: "text", text } },
|
|
274
|
-
idempotencyKey: options.idempotencyKey ?? idempotencyKey(),
|
|
275
|
-
})).data,
|
|
276
|
-
update: async (id, input, options) => (await this.#transport.request("PATCH", `/tasks/${encodeURIComponent(id)}`, {
|
|
277
|
-
...options,
|
|
278
|
-
body: input,
|
|
279
|
-
})).data,
|
|
280
|
-
cancel: async (id, options) => (await this.#transport.request("POST", `/tasks/${encodeURIComponent(id)}/cancel`, { ...options, body: {} })).data,
|
|
281
|
-
};
|
|
282
|
-
repositories = {
|
|
283
|
-
getTask: async (taskId, options) => (await this.#transport.request("GET", `/tasks/${encodeURIComponent(taskId)}/repository`, options)).data,
|
|
284
|
-
claim: async (taskId, input, options) => (await this.#transport.request("POST", `/tasks/${encodeURIComponent(taskId)}/repository`, { ...options, body: input })).data,
|
|
285
|
-
update: async (taskId, input, options) => (await this.#transport.request("PATCH", `/tasks/${encodeURIComponent(taskId)}/repository`, { ...options, body: input })).data,
|
|
286
|
-
};
|
|
287
|
-
github = {
|
|
288
|
-
repositories: async (options) => (await this.#transport.request("GET", "/github/repositories", options)).data,
|
|
289
|
-
openPullRequest: async (taskId, input, options) => (await this.#transport.request("POST", `/github/pull-requests?taskId=${encodeURIComponent(taskId)}`, { ...options, body: input })).data,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
export class ConversationHandle {
|
|
293
|
-
id;
|
|
294
|
-
conversation;
|
|
295
|
-
#totem;
|
|
296
|
-
#recipient;
|
|
297
|
-
constructor(totem, conversation, recipient) {
|
|
298
|
-
this.#totem = totem;
|
|
299
|
-
this.conversation = conversation;
|
|
300
|
-
this.id = conversation.id;
|
|
301
|
-
this.#recipient = recipient;
|
|
302
|
-
}
|
|
303
|
-
send(text, options) {
|
|
304
|
-
return this.#totem.messages.send({ to: this.#recipient, text, conversationId: this.id }, options);
|
|
305
|
-
}
|
|
306
|
-
messages(query = {}, options) {
|
|
307
|
-
return this.#totem.conversations.messages(this.id, query, options);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
export class ConnectedAgent {
|
|
311
|
-
identity;
|
|
312
|
-
address;
|
|
313
|
-
client;
|
|
314
|
-
#subscription;
|
|
315
|
-
#lastError;
|
|
316
|
-
constructor(client, identity) {
|
|
317
|
-
this.client = client;
|
|
318
|
-
this.identity = identity;
|
|
319
|
-
this.address = identity.address;
|
|
320
|
-
}
|
|
321
|
-
get lastError() {
|
|
322
|
-
return this.#lastError;
|
|
323
|
-
}
|
|
324
|
-
send(input, textOrOptions, options) {
|
|
325
|
-
return typeof input === "string"
|
|
326
|
-
? this.client.send(input, textOrOptions, options)
|
|
327
|
-
: this.client.send(input, textOrOptions);
|
|
328
|
-
}
|
|
329
|
-
startConversation(input) {
|
|
330
|
-
return this.client.startConversation(input);
|
|
331
|
-
}
|
|
332
|
-
on(event, handler, options = {}) {
|
|
333
|
-
if (event !== "message")
|
|
334
|
-
throw new TotemError("Only message subscriptions are supported.", { code: "INVALID_EVENT" });
|
|
335
|
-
if (this.#subscription)
|
|
336
|
-
throw new TotemError("This agent already has a message listener.", {
|
|
337
|
-
code: "ALREADY_LISTENING",
|
|
338
|
-
});
|
|
339
|
-
const interval = options.pollIntervalMs ?? 1000;
|
|
340
|
-
if (!Number.isFinite(interval) || interval < 10)
|
|
341
|
-
throw new TotemError("pollIntervalMs must be at least 10.", { code: "INVALID_CONFIG" });
|
|
342
|
-
const controller = new AbortController();
|
|
343
|
-
const inspector = options.inspector ?? createLocalInspector();
|
|
344
|
-
const blocked = new Set();
|
|
345
|
-
const report = (cause) => {
|
|
346
|
-
this.#lastError = cause instanceof Error ? cause : new Error(String(cause));
|
|
347
|
-
try {
|
|
348
|
-
options.onError?.(this.#lastError);
|
|
349
|
-
}
|
|
350
|
-
catch {
|
|
351
|
-
/* Error reporting cannot acknowledge an unprocessed message. */
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
const done = (async () => {
|
|
355
|
-
while (!controller.signal.aborted) {
|
|
356
|
-
try {
|
|
357
|
-
for await (const message of this.client.messages.iterate({ direction: "inbound", unread: true, limit: 100 }, { signal: controller.signal })) {
|
|
358
|
-
if (controller.signal.aborted)
|
|
359
|
-
break;
|
|
360
|
-
if (blocked.has(message.id))
|
|
361
|
-
continue;
|
|
362
|
-
try {
|
|
363
|
-
if (message.content.type === "encrypted")
|
|
364
|
-
throw new TotemError("Use E2eeTotem and pinned endpoint keys to process encrypted messages.", { code: "E2EE_CLIENT_REQUIRED" });
|
|
365
|
-
await inspector.assertAllowed("input", { content: message.content, metadata: message.metadata ?? {} }, { signal: controller.signal });
|
|
366
|
-
const files = message.attachments ?? [];
|
|
367
|
-
const ids = new Set(files.map((file) => file.id));
|
|
368
|
-
if (message.content.type === "text" &&
|
|
369
|
-
message.content.attachments?.some((id) => !ids.has(id)))
|
|
370
|
-
throw new InspectionError("INSPECTION_UNSUPPORTED_FILE");
|
|
371
|
-
for (const file of files) {
|
|
372
|
-
const downloaded = await this.client.attachments.download(file.id, {
|
|
373
|
-
signal: controller.signal,
|
|
374
|
-
});
|
|
375
|
-
await inspector.inspectFile(file.name, file.mediaType, downloaded.bytes, {
|
|
376
|
-
signal: controller.signal,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
const incoming = {
|
|
380
|
-
...message,
|
|
381
|
-
text: message.content.type === "text"
|
|
382
|
-
? message.content.text
|
|
383
|
-
: JSON.stringify(message.content.data),
|
|
384
|
-
reply: (text, writeOptions = {}) => inspector.runAction({ id: message.id, text }, (input) => this.client.messages.reply(input.id, input.text, {
|
|
385
|
-
...writeOptions,
|
|
386
|
-
idempotencyKey: writeOptions.idempotencyKey ?? `reply:${message.id}:0`,
|
|
387
|
-
}), { signal: controller.signal }),
|
|
388
|
-
ack: () => this.client.messages.ack(message.id),
|
|
389
|
-
};
|
|
390
|
-
await handler(incoming);
|
|
391
|
-
await incoming.ack();
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
if (error instanceof InspectionError && error.code !== "INSPECTION_UNAVAILABLE")
|
|
395
|
-
blocked.add(message.id);
|
|
396
|
-
report(error);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
catch (error) {
|
|
401
|
-
if (!controller.signal.aborted)
|
|
402
|
-
report(error);
|
|
403
|
-
}
|
|
404
|
-
if (!controller.signal.aborted) {
|
|
405
|
-
try {
|
|
406
|
-
await sleep(interval, controller.signal);
|
|
407
|
-
}
|
|
408
|
-
catch {
|
|
409
|
-
/* stop wakes the poller. */
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
})();
|
|
414
|
-
const subscription = {
|
|
415
|
-
done,
|
|
416
|
-
stop: async () => {
|
|
417
|
-
controller.abort();
|
|
418
|
-
await done;
|
|
419
|
-
if (this.#subscription === subscription)
|
|
420
|
-
this.#subscription = undefined;
|
|
421
|
-
},
|
|
422
|
-
};
|
|
423
|
-
this.#subscription = subscription;
|
|
424
|
-
return subscription;
|
|
425
|
-
}
|
|
426
|
-
async stop() {
|
|
427
|
-
await this.#subscription?.stop();
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
/** Compatibility name for callers who prefer `new TotemAgent({ address, apiKey })`. */
|
|
431
|
-
export class TotemAgent extends Totem {
|
|
432
|
-
}
|
|
433
|
-
export { createLocalInspector, InspectionError };
|
|
1
|
+
import{TotemError}from"./errors.js";import{createLocalInspector,InspectionError}from"./inspector.js";import{idempotencyKey,sleep,Transport}from"./transport.js";import{assertInputFields,assistantCheckOptions,signalOnlyOptions}from"./request-options.js";export*from"./types.js";export*from"./errors.js";export*from"./actions.js";import{idempotencyKey as idempotencyKey2}from"./transport.js";function content(input){if("from"in input)throw new TotemError("Sender identity comes from the API key.",{code:"INVALID_MESSAGE"});return input.content??{type:"text",text:input.text??input.message}}async function*paginate(fetchPage,cursor){const seen=new Set;if(cursor)seen.add(cursor);do{const page=await fetchPage(cursor);for(const item of page.data)yield item;cursor=page.nextCursor??void 0;if(cursor&&seen.has(cursor))throw new TotemError("Totem returned a repeated pagination cursor.",{code:"INVALID_RESPONSE"});if(cursor)seen.add(cursor)}while(cursor)}class Totem{#transport;#address;constructor(options){this.#transport=new Transport(options);this.#address=options.address?.trim().toLowerCase()}static async connect(options){return new Totem(options).connect()}async connect(){const identity=await this.agent();if(this.#address&&this.#address!==identity.address.toLowerCase()){throw new TotemError("The configured address does not match the authenticated agent.",{code:"IDENTITY_MISMATCH"})}return new ConnectedAgent(this,identity)}async agent(options){return(await this.#transport.request("GET","/agents/me",options)).data}activity={list:(query={},options)=>this.#transport.request("GET","/activity",{...signalOnlyOptions(options),query})};actions={list:(query={},options)=>this.#transport.request("GET","/actions",{...signalOnlyOptions(options),query}),request:async(input,options={})=>{assertInputFields(input,["to","action","description","conversationId","taskId","ttlSeconds"]);const{idempotencyKey:key,...requestOptions}=options;return(await this.#transport.request("POST","/actions",{...signalOnlyOptions(requestOptions),body:input,idempotencyKey:key??idempotencyKey()})).data},get:async(id,options)=>(await this.#transport.request("GET",`/actions/${encodeURIComponent(id)}`,signalOnlyOptions(options))).data,dispatch:async(id,input,options)=>{assertInputFields(input,["digest","action"]);return(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/dispatch`,{...signalOnlyOptions(options),body:input})).data},complete:async(id,input,options)=>{assertInputFields(input,["executionId","outcome","summary"]);return(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/complete`,{...signalOnlyOptions(options),body:input,retrySafe:true})).data},cancel:async(id,options)=>(await this.#transport.request("POST",`/actions/${encodeURIComponent(id)}/cancel`,{...signalOnlyOptions(options),retrySafe:true})).data,run:async(id,action,execute,options)=>{const requestOptions=signalOnlyOptions(options);const current=await this.actions.get(id,requestOptions);const dispatched=await this.actions.dispatch(id,{action,digest:current.digest},requestOptions);if(!dispatched.executionAuthorized||!dispatched.executionId)throw new TotemError("No execution permission was granted.",{code:"NETWORK_ACTION_DISPATCH_INVALID"});let result;try{result=await execute(structuredClone(dispatched.action),dispatched.executionId)}catch(cause){try{await this.actions.complete(id,{executionId:dispatched.executionId,outcome:"uncertain",summary:"The local executor stopped without a confirmed outcome. Inspect before repeating any effect."})}catch{}throw new TotemError("Action outcome uncertain. Inspect it; do not repeat.",{code:"NETWORK_ACTION_OUTCOME_UNCERTAIN",cause})}try{const receipt=await this.actions.complete(id,{executionId:dispatched.executionId,outcome:"succeeded",summary:result.summary},requestOptions);return{value:result.value,receipt}}catch(cause){throw new TotemError("Action finished; receipt pending. Reconcile it without reexecuting.",{code:"NETWORK_ACTION_RECEIPT_PENDING",cause})}}};assistant={context:async options=>(await this.#transport.request("GET","/assistant/context",signalOnlyOptions(options))).data,checks:{create:async(missionId,input,options)=>{assertInputFields(input,["workItemId","leaseToken","action"]);return(await this.#transport.request("POST",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks`,{...assistantCheckOptions(options),body:input})).data},list:async(missionId,workItemId,options)=>(await this.#transport.request("GET",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks`,{...signalOnlyOptions(options),query:{workItemId}})).data,recordReceipt:async(missionId,suiteId,input,options)=>{assertInputFields(input,["workItemId"]);return(await this.#transport.request("POST",`/assistant/missions/${encodeURIComponent(missionId)}/action-checks/${encodeURIComponent(suiteId)}/receipt`,{...signalOnlyOptions(options),body:input})).data}},permissions:async(missionId,options)=>(await this.#transport.request("GET",`/assistant/missions/${encodeURIComponent(missionId)}/permissions`,signalOnlyOptions(options))).data};async resolve(address,options){return(await this.#transport.request("GET",`/resolve/${encodeURIComponent(address)}`,options)).data}agents={search:(query={},options)=>this.#transport.request("GET","/agents/search",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.agents.search({...query,cursor},options),query.cursor)};attachments={upload:async(name,mediaType,bytes,options={})=>{if(!bytes.byteLength||bytes.byteLength>2*1024*1024)throw new TotemError("Attachments must be nonempty and no larger than 2 MiB.",{code:"ATTACHMENT_SIZE_INVALID"});return(await this.#transport.request("POST","/attachments",{...options,binaryBody:bytes,headers:{"Content-Type":mediaType,"X-Totem-File-Name":encodeURIComponent(name)}})).data},download:(id,options)=>this.#transport.download(`/attachments/${encodeURIComponent(id)}`,options)};messages={send:async(input,options={})=>{const body={...input,content:content(input)};delete body.text;delete body.message;return(await this.#transport.request("POST","/messages",{...options,body,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data},list:(query={},options)=>this.#transport.request("GET","/messages",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.messages.list({...query,cursor},options),query.cursor),get:async(id,options)=>(await this.#transport.request("GET",`/messages/${encodeURIComponent(id)}`,options)).data,reply:async(id,text,options={})=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/reply`,{...options,body:{content:{type:"text",text,...options.attachments?{attachments:options.attachments}:{}},...options.metadata?{metadata:options.metadata}:{}},idempotencyKey:options.idempotencyKey??`sdk:reply:${id}`})).data,replyContent:async(id,content2,options={})=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/reply`,{...options,body:{content:content2},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,ack:async(id,options)=>(await this.#transport.request("POST",`/messages/${encodeURIComponent(id)}/ack`,{...options,body:{},retrySafe:true})).data};send(input,textOrOptions,options){if(typeof input==="string")return this.messages.send({to:input,text:textOrOptions},options);return this.messages.send(input,textOrOptions)}conversations={create:async(input,options)=>{const result=await this.#transport.request("POST","/conversations",{...options,body:input});return new ConversationHandle(this,result.data,input.with)},list:(query={},options)=>this.#transport.request("GET","/conversations",{...options,query}),get:async(id,options)=>(await this.#transport.request("GET",`/conversations/${encodeURIComponent(id)}`,options)).data,messages:(id,query={},options)=>this.#transport.request("GET",`/conversations/${encodeURIComponent(id)}/messages`,{...options,query})};startConversation(input,options){return this.conversations.create(input,options)}missions={list:async options=>(await this.#transport.request("GET","/missions",options)).data.missions,listPage:async(query={},options)=>{const page=await this.#transport.request("GET","/missions",{...options,query});return{data:page.data.missions,nextCursor:page.nextCursor??null}},iterate:(query={},options)=>paginate(cursor=>this.missions.listPage({...query,cursor},options),query.cursor),get:async(id,options)=>{const result=await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}`,options);return{...result.data.mission,members:result.data.members,workItems:result.data.workItems,artifacts:result.data.artifacts,events:result.data.events,budget:result.data.budget,coordination:result.data.coordination}},coordination:async(id,version,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/coordination`,{...options,query:{version}})).data,sessions:{report:async(id,input,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/sessions`,{...options,body:input,retrySafe:true})).data},workItems:async(id,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/work-items`,options)).data.workItems,workItemsPage:async(id,query={},options)=>{const page=await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/work-items`,{...options,query});return{data:page.data.workItems,nextCursor:page.nextCursor??null}},iterateWorkItems:(id,query={},options)=>paginate(cursor=>this.missions.workItemsPage(id,{...query,cursor},options),query.cursor),assign:async(id,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,claim:async(id,workItemId,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/claim`,{...options,body:{}})).data,renew:async(id,workItemId,leaseToken,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/renew`,{...options,body:{leaseToken},retrySafe:true})).data,complete:async(id,workItemId,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/work-items/${encodeURIComponent(workItemId)}/complete`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,reserveEffect:async(id,input,options={})=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,listEffects:async(id,workItemId,options)=>(await this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/effects`,{...options,query:{workItemId}})).data.effects,dispatchEffect:async(id,effectId,leaseToken,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects/${encodeURIComponent(effectId)}/dispatch`,{...options,body:{leaseToken}})).data,settleEffect:async(id,effectId,input,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/effects/${encodeURIComponent(effectId)}/settle`,{...options,body:input,retrySafe:true})).data,events:{list:(id,query={},options)=>this.#transport.request("GET",`/missions/${encodeURIComponent(id)}/events`,{...options,query}),iterate:(id,query={},options)=>paginate(cursor=>this.missions.events.list(id,{...query,cursor},options),query.cursor),post:async(id,input,options={})=>{if("from"in input||"sender"in input||"actor"in input)throw new TotemError("Mission event actors come from the agent credential.",{code:"INVALID_EVENT"});return(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/events`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data},ack:async(id,eventId,options)=>(await this.#transport.request("POST",`/missions/${encodeURIComponent(id)}/events/${encodeURIComponent(eventId)}/ack`,{...options,body:{},retrySafe:true})).data}};chats={list:(query={},options)=>this.#transport.request("GET","/chats",{...options,query}),get:async(id,options={})=>(await this.#transport.request("GET",`/chats/${encodeURIComponent(id)}`,{signal:options.signal,query:{cursor:options.cursor,before:options.before,limit:options.limit}})).data,updates:async(id,text,options={})=>(await this.#transport.request("POST",`/chats/${encodeURIComponent(id)}/updates`,{...options,body:{text},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data};chatCommands={list:(query={},options)=>this.#transport.request("GET","/chat-commands",{...options,query}),claim:async(id,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/claim`,{...options,body:{}})).data,renew:async(id,leaseToken,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/renew`,{...options,body:{leaseToken},retrySafe:true})).data,complete:async(id,input,options={})=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/complete`,{...options,body:input,idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,fail:async(id,input,options)=>(await this.#transport.request("POST",`/chat-commands/${encodeURIComponent(id)}/fail`,{...options,body:input,retrySafe:true})).data};runtime={activations:async options=>(await this.#transport.request("GET","/runtime/activations",options)).data,reportActivation:async(input,options)=>(await this.#transport.request("POST","/runtime/activations",{...options,body:input,retrySafe:true})).data,start:async(input,options)=>(await this.#transport.request("POST","/runtime/start",{...options,body:input,retrySafe:true})).data,probe:async(runId,options)=>(await this.#transport.request("GET","/runtime/probe",{...options,query:{runId}})).data,replyProbe:async(input,options)=>(await this.#transport.request("POST","/runtime/probe",{...options,body:input,retrySafe:true})).data,heartbeat:async(input={},options)=>(await this.#transport.request("POST","/runtime/heartbeat",{...options,body:input,retrySafe:true})).data};tasks={create:async(input,options={})=>(await this.#transport.request("POST","/tasks",{...options,body:{to:input.to,content:content(input),conversationId:input.conversationId,metadata:input.metadata,ttlSeconds:input.ttlSeconds,authorization:input.authorization},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,get:async(id,options)=>(await this.#transport.request("GET",`/tasks/${encodeURIComponent(id)}`,options)).data,list:(query={},options)=>this.#transport.request("GET","/tasks",{...options,query}),iterate:(query={},options)=>paginate(cursor=>this.tasks.list({...query,cursor},options),query.cursor),postUpdate:async(id,text,options={})=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(id)}/messages`,{...options,body:{content:{type:"text",text}},idempotencyKey:options.idempotencyKey??idempotencyKey()})).data,update:async(id,input,options)=>(await this.#transport.request("PATCH",`/tasks/${encodeURIComponent(id)}`,{...options,body:input})).data,cancel:async(id,options)=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(id)}/cancel`,{...options,body:{}})).data};repositories={getTask:async(taskId,options)=>(await this.#transport.request("GET",`/tasks/${encodeURIComponent(taskId)}/repository`,options)).data,claim:async(taskId,input,options)=>(await this.#transport.request("POST",`/tasks/${encodeURIComponent(taskId)}/repository`,{...options,body:input})).data,update:async(taskId,input,options)=>(await this.#transport.request("PATCH",`/tasks/${encodeURIComponent(taskId)}/repository`,{...options,body:input})).data};github={repositories:async options=>(await this.#transport.request("GET","/github/repositories",options)).data,openPullRequest:async(taskId,input,options)=>(await this.#transport.request("POST",`/github/pull-requests?taskId=${encodeURIComponent(taskId)}`,{...options,body:input})).data}}class ConversationHandle{id;conversation;#totem;#recipient;constructor(totem,conversation,recipient){this.#totem=totem;this.conversation=conversation;this.id=conversation.id;this.#recipient=recipient}send(text,options){return this.#totem.messages.send({to:this.#recipient,text,conversationId:this.id},options)}messages(query={},options){return this.#totem.conversations.messages(this.id,query,options)}}class ConnectedAgent{identity;address;client;#subscription;#lastError;constructor(client,identity){this.client=client;this.identity=identity;this.address=identity.address}get lastError(){return this.#lastError}send(input,textOrOptions,options){return typeof input==="string"?this.client.send(input,textOrOptions,options):this.client.send(input,textOrOptions)}startConversation(input){return this.client.startConversation(input)}on(event,handler,options={}){if(event!=="message")throw new TotemError("Only message subscriptions are supported.",{code:"INVALID_EVENT"});if(this.#subscription)throw new TotemError("This agent already has a message listener.",{code:"ALREADY_LISTENING"});const interval=options.pollIntervalMs??1e3;if(!Number.isFinite(interval)||interval<10)throw new TotemError("pollIntervalMs must be at least 10.",{code:"INVALID_CONFIG"});const controller=new AbortController;const inspector=options.inspector??createLocalInspector();const blocked=new Set;const report=cause=>{this.#lastError=cause instanceof Error?cause:new Error(String(cause));try{options.onError?.(this.#lastError)}catch{}};const done=(async()=>{while(!controller.signal.aborted){try{for await(const message of this.client.messages.iterate({direction:"inbound",unread:true,limit:100},{signal:controller.signal})){if(controller.signal.aborted)break;if(blocked.has(message.id))continue;try{if(message.content.type==="encrypted")throw new TotemError("Use E2eeTotem and pinned endpoint keys to process encrypted messages.",{code:"E2EE_CLIENT_REQUIRED"});await inspector.assertAllowed("input",{content:message.content,metadata:message.metadata??{}},{signal:controller.signal});const files=message.attachments??[];const ids=new Set(files.map(file=>file.id));if(message.content.type==="text"&&message.content.attachments?.some(id=>!ids.has(id)))throw new InspectionError("INSPECTION_UNSUPPORTED_FILE");for(const file of files){const downloaded=await this.client.attachments.download(file.id,{signal:controller.signal});await inspector.inspectFile(file.name,file.mediaType,downloaded.bytes,{signal:controller.signal})}const incoming={...message,text:message.content.type==="text"?message.content.text:JSON.stringify(message.content.data),reply:(text,writeOptions={})=>inspector.runAction({id:message.id,text},input=>this.client.messages.reply(input.id,input.text,{...writeOptions,idempotencyKey:writeOptions.idempotencyKey??`reply:${message.id}:0`}),{signal:controller.signal}),ack:()=>this.client.messages.ack(message.id)};await handler(incoming);await incoming.ack()}catch(error){if(error instanceof InspectionError&&error.code!=="INSPECTION_UNAVAILABLE")blocked.add(message.id);report(error)}}}catch(error){if(!controller.signal.aborted)report(error)}if(!controller.signal.aborted){try{await sleep(interval,controller.signal)}catch{}}}})();const subscription={done,stop:async()=>{controller.abort();await done;if(this.#subscription===subscription)this.#subscription=void 0}};this.#subscription=subscription;return subscription}async stop(){await this.#subscription?.stop()}}class TotemAgent extends Totem{}export{ConnectedAgent,ConversationHandle,InspectionError,Totem,TotemAgent,createLocalInspector,idempotencyKey2 as idempotencyKey};
|