managed-deepagents 0.10.0-dev.15 → 0.10.0-dev.17
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 +63 -32
- package/dist/channels/github/index.d.ts.map +1 -1
- package/dist/channels/github/index.js +5 -3
- package/dist/channels/github/index.js.map +1 -1
- package/dist/channels/github/mentions.d.ts +8 -3
- package/dist/channels/github/mentions.d.ts.map +1 -1
- package/dist/channels/github/mentions.js +113 -40
- package/dist/channels/github/mentions.js.map +1 -1
- package/dist/channels/github/schema.d.ts +150 -96
- package/dist/channels/github/schema.d.ts.map +1 -1
- package/dist/channels/github/schema.js +18 -5
- package/dist/channels/github/schema.js.map +1 -1
- package/dist/channels/github/types.d.ts +14 -10
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/http/endpoint.d.ts +28 -19
- package/dist/channels/http/endpoint.d.ts.map +1 -1
- package/dist/channels/http/endpoint.js +107 -37
- package/dist/channels/http/endpoint.js.map +1 -1
- package/dist/channels/http/http.d.ts.map +1 -1
- package/dist/channels/http/http.js +130 -47
- package/dist/channels/http/http.js.map +1 -1
- package/dist/channels/http/index.d.ts +1 -1
- package/dist/channels/http/index.d.ts.map +1 -1
- package/dist/channels/http/index.js.map +1 -1
- package/dist/channels/http/ingress.d.ts +17 -2
- package/dist/channels/http/ingress.d.ts.map +1 -1
- package/dist/channels/http/ingress.js +19 -5
- package/dist/channels/http/ingress.js.map +1 -1
- package/dist/channels/http/provider.d.ts +14 -11
- package/dist/channels/http/provider.d.ts.map +1 -1
- package/dist/channels/http/provider.js +19 -8
- package/dist/channels/http/provider.js.map +1 -1
- package/dist/channels/http/studio.d.ts +4 -3
- package/dist/channels/http/studio.d.ts.map +1 -1
- package/dist/channels/http/studio.js +9 -4
- package/dist/channels/http/studio.js.map +1 -1
- package/dist/channels/http/types.d.ts +57 -16
- package/dist/channels/http/types.d.ts.map +1 -1
- package/dist/channels/linear/index.d.ts.map +1 -1
- package/dist/channels/linear/index.js +10 -7
- package/dist/channels/linear/index.js.map +1 -1
- package/dist/channels/linear/types.d.ts +8 -6
- package/dist/channels/linear/types.d.ts.map +1 -1
- package/dist/channels/run-input.d.ts +9 -3
- package/dist/channels/run-input.d.ts.map +1 -1
- package/dist/channels/run-input.js +12 -7
- package/dist/channels/run-input.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { ChannelRunResult } from "../run-input.js";
|
|
3
|
-
import type { JsonValue } from "../types.js";
|
|
4
3
|
/**
|
|
5
4
|
* The deployment's env var holding the secret it shares with your services:
|
|
6
5
|
* every event endpoint request is signed with it, as Standard Webhooks does.
|
|
@@ -21,23 +20,22 @@ export declare function signingKey(secret: string): Buffer;
|
|
|
21
20
|
* without a key. Each key signs `<id>.<timestamp>.<body>`.
|
|
22
21
|
*/
|
|
23
22
|
export declare function signatureHeaders(keys: readonly Buffer[], id: string, timestamp: number, body: string): Record<string, string>;
|
|
24
|
-
/** A channel's event, by the fields
|
|
23
|
+
/** A channel's event, by the fields every endpoint is sent. */
|
|
25
24
|
type ChannelEvent = {
|
|
26
25
|
key: string;
|
|
27
26
|
deliveryId: string;
|
|
28
27
|
payload: unknown;
|
|
29
28
|
};
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
* delivery ID and payload, such as the handles a GitHub event mentions.
|
|
33
|
-
*/
|
|
34
|
-
export type EventFields<E> = (event: E) => Record<string, JsonValue>;
|
|
29
|
+
/** The fields of an event every endpoint is sent. */
|
|
30
|
+
declare const EVENT_FIELDS: readonly ["key", "deliveryId", "payload"];
|
|
35
31
|
/**
|
|
36
32
|
* The event a channel sends its endpoint: the event key, the provider's
|
|
37
|
-
* delivery ID and its payload, and the channel's own fields `X
|
|
38
|
-
*
|
|
33
|
+
* delivery ID and its payload, and the channel's own fields `X`, such as
|
|
34
|
+
* GitHub's `repository` and `mentions`. A channel's own fields are optional,
|
|
35
|
+
* so a receiver takes requests from a deployment that sends fewer. For a
|
|
36
|
+
* union of events, each is typed by the key.
|
|
39
37
|
*/
|
|
40
|
-
export type EndpointEvent<E extends ChannelEvent = ChannelEvent, X extends keyof E = never> = E extends unknown ? Pick<E,
|
|
38
|
+
export type EndpointEvent<E extends ChannelEvent = ChannelEvent, X extends keyof E = never> = E extends unknown ? Pick<E, (typeof EVENT_FIELDS)[number]> & Partial<Pick<E, X>> : never;
|
|
41
39
|
/** The conversation's thread an endpoint request is about. */
|
|
42
40
|
export interface EndpointThread {
|
|
43
41
|
/** The thread the run starts on, or that waits on an interrupt. */
|
|
@@ -54,9 +52,9 @@ export interface EndpointRequest<E extends ChannelEvent = ChannelEvent, X extend
|
|
|
54
52
|
thread: EndpointThread;
|
|
55
53
|
}
|
|
56
54
|
declare const answerSchema: z.ZodObject<{
|
|
57
|
-
input: z.ZodCustom<JsonValue, JsonValue>;
|
|
58
|
-
context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCustom<JsonValue, JsonValue>>>;
|
|
59
|
-
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCustom<JsonValue, JsonValue>>>;
|
|
55
|
+
input: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodCustom<import("../types.js").JsonValue, import("../types.js").JsonValue>>]>;
|
|
56
|
+
context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCustom<import("../types.js").JsonValue, import("../types.js").JsonValue>>>;
|
|
57
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCustom<import("../types.js").JsonValue, import("../types.js").JsonValue>>>;
|
|
60
58
|
webhook: z.ZodOptional<z.ZodString>;
|
|
61
59
|
}, z.core.$strict>;
|
|
62
60
|
/**
|
|
@@ -66,19 +64,30 @@ declare const answerSchema: z.ZodObject<{
|
|
|
66
64
|
* The channel checks an answer against the same schema.
|
|
67
65
|
*/
|
|
68
66
|
export type EndpointAnswer = z.infer<typeof answerSchema>;
|
|
67
|
+
/** The handler an endpoint stands for; see {@link endpointHandler}. */
|
|
68
|
+
export type EndpointCallback<E extends ChannelEvent = ChannelEvent> = (event: E, thread: EndpointThread) => Promise<ChannelRunResult | null>;
|
|
69
|
+
/** Whether a handler is one an endpoint stands for. */
|
|
70
|
+
export declare function isEndpointCallback(handler: unknown): boolean;
|
|
69
71
|
/** Whether an `onEvent` value is an endpoint rather than a handler function. */
|
|
70
72
|
export declare function isEventEndpoint(value: unknown): value is EventEndpoint;
|
|
71
73
|
/**
|
|
72
74
|
* The handler an endpoint stands for. When the event's thread takes a turn,
|
|
73
75
|
* MDA POSTs `{ event: { key, deliveryId, payload }, thread: { id, channel,
|
|
74
|
-
* interrupted? } }` as JSON to the endpoint's URL, with the
|
|
75
|
-
*
|
|
76
|
+
* interrupted? } }` as JSON to the endpoint's URL, with the event's `fields`,
|
|
77
|
+
* the channel's own, beside the payload, and a `Request`'s headers,
|
|
76
78
|
* signed when the deployment has `MDA_SERVICE_SECRET`, and starts the
|
|
77
79
|
* run the service answers with: `{ input, context?, metadata?, webhook? }`.
|
|
78
|
-
* `null` or HTTP 204 starts nothing.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
80
|
+
* `null` or HTTP 204 starts nothing. A network error before the answer, HTTP
|
|
81
|
+
* 429 or a 5xx is tried again, up to three attempts. Any other answer, one cut
|
|
82
|
+
* off, a redirect, or no answer within 60 seconds fails the handler, as a
|
|
83
|
+
* handler that throws does.
|
|
84
|
+
* The URL is https, or http to a loopback host for local development, without
|
|
85
|
+
* credentials in it.
|
|
86
|
+
*
|
|
87
|
+
* The webhook ID is `msg_<deliveryId>` for the call that starts the run, and
|
|
88
|
+
* `msg_<deliveryId>_interrupted` for the one that asks whether to tell the
|
|
89
|
+
* conversation its thread waits on a decision.
|
|
81
90
|
*/
|
|
82
|
-
export declare function endpointHandler<E extends ChannelEvent = ChannelEvent>(endpoint: EventEndpoint, label: string, fields?:
|
|
91
|
+
export declare function endpointHandler<E extends ChannelEvent = ChannelEvent, X extends keyof E & string = never>(endpoint: EventEndpoint, label: string, fields?: readonly X[]): EndpointCallback<E>;
|
|
83
92
|
export {};
|
|
84
93
|
//# sourceMappingURL=endpoint.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../../../src/channels/http/endpoint.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../../../src/channels/http/endpoint.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAcxD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAQvD;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC;AAmBnD,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAejD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxB;AAED,+DAA+D;AAC/D,KAAK,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1E,qDAAqD;AACrD,QAAA,MAAM,YAAY,YAAI,KAAK,EAAE,YAAY,EAAE,SAAS,CAAU,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,CACvB,CAAC,SAAS,YAAY,GAAG,YAAY,EACrC,CAAC,SAAS,MAAM,CAAC,GAAG,KAAK,IACvB,CAAC,SAAS,OAAO,GACjB,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC5D,KAAK,CAAC;AAEV,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,qFAAqF;IACrF,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe,CAC9B,CAAC,SAAS,YAAY,GAAG,YAAY,EACrC,CAAC,SAAS,MAAM,CAAC,GAAG,KAAK;IAEzB,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,QAAA,MAAM,YAAY;;;;;kBAYhB,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAE1D,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,IAAI,CACpE,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;AAKtC,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE5D;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAMtE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,YAAY,GAAG,YAAY,EACrC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,EAElC,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,SAAS,CAAC,EAAO,GACxB,gBAAgB,CAAC,CAAC,CAAC,CA8CrB"}
|
|
@@ -6,8 +6,14 @@
|
|
|
6
6
|
import { createHmac } from "node:crypto";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { jsonValueSchema } from "../message-schema.js";
|
|
9
|
-
/** How long the service may take to answer. */
|
|
9
|
+
/** How long the service may take to answer one attempt. */
|
|
10
10
|
const TIMEOUT_MS = 60_000;
|
|
11
|
+
/**
|
|
12
|
+
* The pauses before trying again after a network error, HTTP 429 or a 5xx:
|
|
13
|
+
* three attempts in all, each with the same webhook ID, so a receiver that
|
|
14
|
+
* dedupes on it acts once.
|
|
15
|
+
*/
|
|
16
|
+
const RETRY_DELAYS_MS = [1_000, 5_000];
|
|
11
17
|
/** The largest answer read; run input may carry images. */
|
|
12
18
|
const MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
13
19
|
const ANSWER_SHAPE = "event endpoint answer must be null or { input, context?, metadata?, webhook? }";
|
|
@@ -70,8 +76,14 @@ export function signatureHeaders(keys, id, timestamp, body) {
|
|
|
70
76
|
.join(" "),
|
|
71
77
|
};
|
|
72
78
|
}
|
|
79
|
+
/** The fields of an event every endpoint is sent. */
|
|
80
|
+
const EVENT_FIELDS = ["key", "deliveryId", "payload"];
|
|
73
81
|
const answerSchema = z.strictObject({
|
|
74
|
-
|
|
82
|
+
/**
|
|
83
|
+
* The run's input: text, or state such as `{ messages }`. Never an array,
|
|
84
|
+
* which a handler's `[input, options]` would be read as.
|
|
85
|
+
*/
|
|
86
|
+
input: z.union([z.string(), z.record(z.string(), jsonValueSchema)]),
|
|
75
87
|
/** Run context, as `agent.invoke(input, { context })` takes it. */
|
|
76
88
|
context: z.record(z.string(), jsonValueSchema).optional(),
|
|
77
89
|
/** Metadata for the run; see `ChannelRunOptions`. */
|
|
@@ -79,6 +91,12 @@ const answerSchema = z.strictObject({
|
|
|
79
91
|
/** A URL the Agent Server POSTs the finished run to; see `ChannelRunOptions`. */
|
|
80
92
|
webhook: z.string().optional(),
|
|
81
93
|
});
|
|
94
|
+
/** The handlers endpoints stand for, so a channel knows to answer first. */
|
|
95
|
+
const endpointCallbacks = new WeakSet();
|
|
96
|
+
/** Whether a handler is one an endpoint stands for. */
|
|
97
|
+
export function isEndpointCallback(handler) {
|
|
98
|
+
return typeof handler === "function" && endpointCallbacks.has(handler);
|
|
99
|
+
}
|
|
82
100
|
/** Whether an `onEvent` value is an endpoint rather than a handler function. */
|
|
83
101
|
export function isEventEndpoint(value) {
|
|
84
102
|
return (typeof value === "string" ||
|
|
@@ -88,28 +106,33 @@ export function isEventEndpoint(value) {
|
|
|
88
106
|
/**
|
|
89
107
|
* The handler an endpoint stands for. When the event's thread takes a turn,
|
|
90
108
|
* MDA POSTs `{ event: { key, deliveryId, payload }, thread: { id, channel,
|
|
91
|
-
* interrupted? } }` as JSON to the endpoint's URL, with the
|
|
92
|
-
*
|
|
109
|
+
* interrupted? } }` as JSON to the endpoint's URL, with the event's `fields`,
|
|
110
|
+
* the channel's own, beside the payload, and a `Request`'s headers,
|
|
93
111
|
* signed when the deployment has `MDA_SERVICE_SECRET`, and starts the
|
|
94
112
|
* run the service answers with: `{ input, context?, metadata?, webhook? }`.
|
|
95
|
-
* `null` or HTTP 204 starts nothing.
|
|
96
|
-
*
|
|
97
|
-
*
|
|
113
|
+
* `null` or HTTP 204 starts nothing. A network error before the answer, HTTP
|
|
114
|
+
* 429 or a 5xx is tried again, up to three attempts. Any other answer, one cut
|
|
115
|
+
* off, a redirect, or no answer within 60 seconds fails the handler, as a
|
|
116
|
+
* handler that throws does.
|
|
117
|
+
* The URL is https, or http to a loopback host for local development, without
|
|
118
|
+
* credentials in it.
|
|
119
|
+
*
|
|
120
|
+
* The webhook ID is `msg_<deliveryId>` for the call that starts the run, and
|
|
121
|
+
* `msg_<deliveryId>_interrupted` for the one that asks whether to tell the
|
|
122
|
+
* conversation its thread waits on a decision.
|
|
98
123
|
*/
|
|
99
|
-
export function endpointHandler(endpoint, label, fields) {
|
|
124
|
+
export function endpointHandler(endpoint, label, fields = []) {
|
|
100
125
|
const template = endpoint instanceof Request ? endpoint : undefined;
|
|
101
126
|
const target = endpointUrl(template ? template.url : String(endpoint), label);
|
|
127
|
+
// A malformed secret throws when the channel is declared, not on each event.
|
|
128
|
+
signingKeys();
|
|
102
129
|
const baseHeaders = new Headers(template?.headers);
|
|
103
130
|
baseHeaders.set("content-type", "application/json");
|
|
104
131
|
baseHeaders.set("accept", "application/json");
|
|
105
|
-
|
|
132
|
+
const sent = [...EVENT_FIELDS, ...fields];
|
|
133
|
+
const callback = async (event, thread) => {
|
|
106
134
|
const request = {
|
|
107
|
-
event:
|
|
108
|
-
...fields?.(event),
|
|
109
|
-
key: event.key,
|
|
110
|
-
deliveryId: event.deliveryId,
|
|
111
|
-
payload: event.payload,
|
|
112
|
-
},
|
|
135
|
+
event: pick(event, sent),
|
|
113
136
|
thread: {
|
|
114
137
|
id: thread.id,
|
|
115
138
|
channel: { name: thread.channel.name },
|
|
@@ -118,28 +141,67 @@ export function endpointHandler(endpoint, label, fields) {
|
|
|
118
141
|
};
|
|
119
142
|
// The exact bytes sent are the ones signed.
|
|
120
143
|
const body = JSON.stringify(request);
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
const id = `msg_${event.deliveryId}${thread.interrupted ? "_interrupted" : ""}`;
|
|
145
|
+
for (let attempt = 0;; attempt++) {
|
|
146
|
+
const response = await post(target, baseHeaders, id, body).catch((error) => {
|
|
147
|
+
// A timeout isn't tried again: the service may still be acting on it.
|
|
148
|
+
if (attempt < RETRY_DELAYS_MS.length && !isTimeout(error))
|
|
149
|
+
return undefined;
|
|
150
|
+
throw error;
|
|
151
|
+
});
|
|
152
|
+
if (response?.status === 204) {
|
|
153
|
+
await response.body?.cancel();
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
if (response?.ok)
|
|
157
|
+
return runResult(await boundedJson(response));
|
|
158
|
+
await response?.body?.cancel();
|
|
159
|
+
if (response &&
|
|
160
|
+
(attempt >= RETRY_DELAYS_MS.length || !retried(response.status)))
|
|
161
|
+
throw new Error(`event endpoint answered HTTP ${response.status}`);
|
|
162
|
+
await sleep(RETRY_DELAYS_MS[attempt]);
|
|
135
163
|
}
|
|
136
|
-
if (!response.ok) {
|
|
137
|
-
await response.body?.cancel();
|
|
138
|
-
throw new Error(`event endpoint answered HTTP ${response.status}`);
|
|
139
|
-
}
|
|
140
|
-
return runResult(await boundedJson(response));
|
|
141
164
|
};
|
|
165
|
+
endpointCallbacks.add(callback);
|
|
166
|
+
return callback;
|
|
167
|
+
}
|
|
168
|
+
/** The event's `fields`; an absent one stays absent. */
|
|
169
|
+
function pick(event, fields) {
|
|
170
|
+
const picked = {};
|
|
171
|
+
for (const field of fields)
|
|
172
|
+
if (event[field] !== undefined)
|
|
173
|
+
picked[field] = event[field];
|
|
174
|
+
// Every field of `EVENT_FIELDS` is one of `fields`.
|
|
175
|
+
return picked;
|
|
176
|
+
}
|
|
177
|
+
/** One attempt, signed afresh: Standard Webhooks timestamps each. */
|
|
178
|
+
function post(target, baseHeaders, id, body) {
|
|
179
|
+
const headers = new Headers(baseHeaders);
|
|
180
|
+
const signature = signatureHeaders(signingKeys(), id, Math.floor(Date.now() / 1000), body);
|
|
181
|
+
for (const [name, value] of Object.entries(signature))
|
|
182
|
+
headers.set(name, value);
|
|
183
|
+
return fetch(target, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers,
|
|
186
|
+
body,
|
|
187
|
+
redirect: "manual",
|
|
188
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
189
|
+
});
|
|
142
190
|
}
|
|
191
|
+
/** Whether an answer's status is worth trying again. */
|
|
192
|
+
function retried(status) {
|
|
193
|
+
return status === 429 || status >= 500;
|
|
194
|
+
}
|
|
195
|
+
function isTimeout(error) {
|
|
196
|
+
return error instanceof DOMException && error.name === "TimeoutError";
|
|
197
|
+
}
|
|
198
|
+
function sleep(ms) {
|
|
199
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The endpoint's URL. Plain http only reaches a loopback host: the answer
|
|
203
|
+
* isn't signed, so anyone on the path could write the run's input otherwise.
|
|
204
|
+
*/
|
|
143
205
|
function endpointUrl(url, label) {
|
|
144
206
|
let parsed;
|
|
145
207
|
try {
|
|
@@ -149,12 +211,20 @@ function endpointUrl(url, label) {
|
|
|
149
211
|
parsed = undefined;
|
|
150
212
|
}
|
|
151
213
|
if (!parsed ||
|
|
152
|
-
(parsed.protocol
|
|
214
|
+
!(parsed.protocol === "https:" ||
|
|
215
|
+
(parsed.protocol === "http:" && isLoopback(parsed.hostname))) ||
|
|
153
216
|
parsed.username ||
|
|
154
217
|
parsed.password)
|
|
155
|
-
throw new TypeError(`${label} must be an absolute
|
|
218
|
+
throw new TypeError(`${label} must be an absolute https URL, or http to localhost, without credentials in it`);
|
|
156
219
|
return parsed.href;
|
|
157
220
|
}
|
|
221
|
+
/** Whether a URL's hostname is this machine. */
|
|
222
|
+
function isLoopback(hostname) {
|
|
223
|
+
return (hostname === "localhost" ||
|
|
224
|
+
hostname.endsWith(".localhost") ||
|
|
225
|
+
hostname === "[::1]" ||
|
|
226
|
+
/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname));
|
|
227
|
+
}
|
|
158
228
|
/** The answer as JSON, read only up to `MAX_RESPONSE_BYTES`. */
|
|
159
229
|
async function boundedJson(response) {
|
|
160
230
|
const declared = Number(response.headers.get("content-length"));
|
|
@@ -201,6 +271,6 @@ function runResult(body) {
|
|
|
201
271
|
if (parsed.data === null)
|
|
202
272
|
return null;
|
|
203
273
|
const { input, ...options } = parsed.data;
|
|
204
|
-
return
|
|
274
|
+
return [input, options];
|
|
205
275
|
}
|
|
206
276
|
//# sourceMappingURL=endpoint.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../../src/channels/http/endpoint.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../../src/channels/http/endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/channels/http/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/channels/http/http.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,kBAAkB,EAOnB,MAAM,YAAY,CAAC;AAapB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAwCrE,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,oBAAoB,EACzB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CA4YnB"}
|
|
@@ -2,7 +2,7 @@ import { channelContextSchema } from "../../run-context.js";
|
|
|
2
2
|
import { immutableCopy, validateMessage } from "../runtime.js";
|
|
3
3
|
import { isJsonValue } from "../message-schema.js";
|
|
4
4
|
import { normalizeRunInput } from "../run-input.js";
|
|
5
|
-
import { cancelHttpIngressRuns, startHttpIngressAgent, observeHttpIngressAgent, waitForHttpIngressThread, } from "./ingress.js";
|
|
5
|
+
import { cancelHttpIngressRuns, httpIngressThreadWaitsOn, startHttpIngressAgent, observeHttpIngressAgent, waitForHttpIngressThread, } from "./ingress.js";
|
|
6
6
|
import { ChannelError } from "../errors.js";
|
|
7
7
|
import { scheduleIngressWork } from "../../ingress/index.js";
|
|
8
8
|
import { logger } from "../../runtime/log.js";
|
|
@@ -146,15 +146,27 @@ export async function handleHttpRequest(ctx, adapter) {
|
|
|
146
146
|
rawEvent: structuredClone(result.rawEvent),
|
|
147
147
|
}),
|
|
148
148
|
});
|
|
149
|
+
// Joined before the first start, so a cancel during it reaches the message.
|
|
150
|
+
const waiter = joinThreadWaiters(threadId);
|
|
151
|
+
/** Set when a cancel dropped the message while its input was decided. */
|
|
152
|
+
let dropped = false;
|
|
149
153
|
const inputHook = adapter.input?.bind(adapter);
|
|
150
154
|
async function prepare() {
|
|
155
|
+
let prepared;
|
|
151
156
|
try {
|
|
152
|
-
|
|
157
|
+
prepared = normalizeRunInput(await inputHook?.(immutableCopy(message), rawEvent === undefined ? undefined : structuredClone(rawEvent)));
|
|
153
158
|
}
|
|
154
159
|
catch (error) {
|
|
155
160
|
logger.error(`channel input failed (${logger.safeErrorLabel(error)})`);
|
|
156
161
|
return undefined;
|
|
157
162
|
}
|
|
163
|
+
// A slow input, such as an endpoint's, may outlast a cancel.
|
|
164
|
+
if (prepared && waiter.cancelled) {
|
|
165
|
+
logger.info(`channel run dropped: its thread was cancelled while its input was decided ${JSON.stringify({ threadId })}`);
|
|
166
|
+
dropped = true;
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
return prepared;
|
|
158
170
|
}
|
|
159
171
|
const startInput = {
|
|
160
172
|
...input,
|
|
@@ -186,15 +198,15 @@ export async function handleHttpRequest(ctx, adapter) {
|
|
|
186
198
|
}, { label: `channel "${ctx.name}" received notice` });
|
|
187
199
|
});
|
|
188
200
|
const onFinish = adapter.onFinish?.bind(adapter);
|
|
189
|
-
|
|
201
|
+
/** Tell the adapter the message is done; the thread's assistant lets a notice link to it. */
|
|
202
|
+
async function finish(done, threadAssistantId) {
|
|
190
203
|
if (!onFinish)
|
|
191
204
|
return;
|
|
192
205
|
await received;
|
|
193
206
|
try {
|
|
194
207
|
await onFinish(immutableCopy(message.target), immutableCopy({
|
|
195
|
-
...delivery(
|
|
196
|
-
...
|
|
197
|
-
interrupts: done.interrupts ?? [],
|
|
208
|
+
...delivery(threadAssistantId ?? assistantId),
|
|
209
|
+
...done,
|
|
198
210
|
}));
|
|
199
211
|
}
|
|
200
212
|
catch (error) {
|
|
@@ -202,21 +214,6 @@ export async function handleHttpRequest(ctx, adapter) {
|
|
|
202
214
|
}
|
|
203
215
|
}
|
|
204
216
|
const label = `channel "${ctx.name}" delivery`;
|
|
205
|
-
// Joined before the first start, so a cancel during it reaches the message.
|
|
206
|
-
const waiter = joinThreadWaiters(threadId);
|
|
207
|
-
let outcome;
|
|
208
|
-
try {
|
|
209
|
-
outcome = await startHttpIngressAgent(startInput);
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
leaveThreadWaiters(threadId, waiter);
|
|
213
|
-
logger.error(`channel run could not be started (${logger.safeErrorLabel(error)})`);
|
|
214
|
-
// No run started and the provider may redeliver: nothing is finished.
|
|
215
|
-
return jsonError(500, "channel run could not be started");
|
|
216
|
-
}
|
|
217
|
-
// Only a message that waits for its busy thread stays.
|
|
218
|
-
if (!("skipped" in outcome && outcome.skipped === "busy"))
|
|
219
|
-
leaveThreadWaiters(threadId, waiter);
|
|
220
217
|
const post = adapter.post?.bind(adapter);
|
|
221
218
|
const onError = adapter.onError?.bind(adapter);
|
|
222
219
|
async function notifyFailed(error) {
|
|
@@ -247,22 +244,21 @@ export async function handleHttpRequest(ctx, adapter) {
|
|
|
247
244
|
outcome = await observeHttpIngressAgent({ ...input, ...started });
|
|
248
245
|
}
|
|
249
246
|
catch (error) {
|
|
250
|
-
// The run may have answered already, so nothing is reported, and how it
|
|
251
|
-
// ended is unknown: it doesn't finish here.
|
|
252
247
|
logger.error(`channel run could not be observed (${logger.safeErrorLabel(error)})`);
|
|
253
|
-
return;
|
|
254
248
|
}
|
|
249
|
+
// The run may have answered already, or may still, so no error is
|
|
250
|
+
// reported; how it ended is unknown.
|
|
251
|
+
if (!outcome)
|
|
252
|
+
return finish({ type: "failed", reason: "observe", interrupts: [] });
|
|
255
253
|
if (outcome.status === "error" || outcome.status === "timeout") {
|
|
256
254
|
logger.error(`channel run failed ${JSON.stringify({ threadId })}`);
|
|
257
255
|
await notifyFailed(new ChannelError("channel_run_failed", "channel run failed"));
|
|
258
256
|
}
|
|
259
257
|
return finish({
|
|
258
|
+
type: "run",
|
|
260
259
|
run: { id: runId, status: outcome.status },
|
|
261
260
|
interrupts: outcome.interrupts,
|
|
262
|
-
|
|
263
|
-
assistantId: outcome.assistantId,
|
|
264
|
-
}),
|
|
265
|
-
});
|
|
261
|
+
}, outcome.assistantId);
|
|
266
262
|
}
|
|
267
263
|
// The thread's current run may still stop for a decision, so the message
|
|
268
264
|
// waits for that run instead of queueing behind it, then checks again.
|
|
@@ -274,14 +270,18 @@ export async function handleHttpRequest(ctx, adapter) {
|
|
|
274
270
|
await waitForHttpIngressThread(input);
|
|
275
271
|
if (waiter.cancelled) {
|
|
276
272
|
logger.info(`channel run dropped: its thread was cancelled while it waited ${JSON.stringify({ threadId })}`);
|
|
277
|
-
return await finish({
|
|
273
|
+
return await finish({
|
|
274
|
+
type: "skipped",
|
|
275
|
+
reason: "dropped",
|
|
276
|
+
interrupts: [],
|
|
277
|
+
});
|
|
278
278
|
}
|
|
279
279
|
next = await startHttpIngressAgent(startInput);
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
catch (error) {
|
|
283
283
|
logger.error(`channel run could not be started (${logger.safeErrorLabel(error)})`);
|
|
284
|
-
return;
|
|
284
|
+
return await finish({ type: "failed", reason: "start", interrupts: [] });
|
|
285
285
|
}
|
|
286
286
|
finally {
|
|
287
287
|
leaveThreadWaiters(threadId, waiter);
|
|
@@ -291,31 +291,88 @@ export async function handleHttpRequest(ctx, adapter) {
|
|
|
291
291
|
logger.info(`channel run skipped ${JSON.stringify({ threadId, reason: next.skipped })}`);
|
|
292
292
|
await finishSkipped(next);
|
|
293
293
|
}
|
|
294
|
-
/** A message the thread took no run for is done; one that waits says on what. */
|
|
294
|
+
/** A message the thread took no run for is done, with why; one that waits says on what. */
|
|
295
295
|
function finishSkipped(skipped) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
:
|
|
296
|
+
switch (skipped.skipped) {
|
|
297
|
+
case "paused":
|
|
298
|
+
return finish({
|
|
299
|
+
type: "skipped",
|
|
300
|
+
reason: "waiting",
|
|
301
|
+
interrupts: skipped.interrupts,
|
|
302
|
+
}, skipped.assistantId);
|
|
303
|
+
case "ignored":
|
|
304
|
+
return finish({
|
|
305
|
+
type: "skipped",
|
|
306
|
+
reason: dropped ? "dropped" : "ignored",
|
|
307
|
+
interrupts: [],
|
|
308
|
+
});
|
|
309
|
+
case "busy":
|
|
310
|
+
throw new Error("a busy thread's message is not done");
|
|
311
|
+
default:
|
|
312
|
+
return finish({
|
|
313
|
+
type: "skipped",
|
|
314
|
+
reason: skipped.skipped,
|
|
315
|
+
interrupts: [],
|
|
316
|
+
});
|
|
317
|
+
}
|
|
304
318
|
}
|
|
305
|
-
|
|
319
|
+
/** Start the message's run; `undefined` when that failed. */
|
|
320
|
+
async function startFirst() {
|
|
321
|
+
let outcome;
|
|
322
|
+
try {
|
|
323
|
+
outcome = await startHttpIngressAgent(startInput);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
leaveThreadWaiters(threadId, waiter);
|
|
327
|
+
logger.error(`channel run could not be started (${logger.safeErrorLabel(error)})`);
|
|
328
|
+
// Answered with an error, the provider may redeliver it, as a new
|
|
329
|
+
// delivery: only one it already accepted is done for good.
|
|
330
|
+
if (onFinish)
|
|
331
|
+
scheduleIngressWork(() => finish({
|
|
332
|
+
type: "failed",
|
|
333
|
+
reason: message.background ? "start" : "retry",
|
|
334
|
+
interrupts: [],
|
|
335
|
+
}), { label });
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
// Only a message that waits for its busy thread stays.
|
|
339
|
+
if (!("skipped" in outcome && outcome.skipped === "busy"))
|
|
340
|
+
leaveThreadWaiters(threadId, waiter);
|
|
341
|
+
return outcome;
|
|
342
|
+
}
|
|
343
|
+
/** What follows the first start, in the background. */
|
|
344
|
+
function followUp(outcome) {
|
|
345
|
+
if (!("skipped" in outcome)) {
|
|
346
|
+
scheduleIngressWork(() => deliver(outcome), { label });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
306
349
|
logger.info(`channel run skipped ${JSON.stringify({ threadId, reason: outcome.skipped })}`);
|
|
307
350
|
const skipped = outcome;
|
|
308
351
|
if (skipped.skipped === "busy")
|
|
309
352
|
scheduleIngressWork(startWhenIdle, { label });
|
|
310
353
|
else if (onFinish)
|
|
311
354
|
scheduleIngressWork(() => finishSkipped(skipped), { label });
|
|
312
|
-
return (result.response ??
|
|
313
|
-
Response.json({ status: skipped.skipped }, { status: 202 }));
|
|
314
355
|
}
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
356
|
+
const accepted = () => result.response ??
|
|
357
|
+
Response.json({ status: "accepted", deliveryId }, { status: 202 });
|
|
358
|
+
// Input that may take longer than the provider waits, such as an
|
|
359
|
+
// endpoint's, is decided after it has its answer.
|
|
360
|
+
if (message.background) {
|
|
361
|
+
scheduleIngressWork(async () => {
|
|
362
|
+
const outcome = await startFirst();
|
|
363
|
+
if (outcome)
|
|
364
|
+
followUp(outcome);
|
|
365
|
+
}, { label });
|
|
366
|
+
return accepted();
|
|
367
|
+
}
|
|
368
|
+
const outcome = await startFirst();
|
|
369
|
+
if (!outcome)
|
|
370
|
+
return jsonError(500, "channel run could not be started");
|
|
371
|
+
followUp(outcome);
|
|
372
|
+
return "skipped" in outcome
|
|
373
|
+
? (result.response ??
|
|
374
|
+
Response.json({ status: outcome.skipped }, { status: 202 }))
|
|
375
|
+
: accepted();
|
|
319
376
|
}
|
|
320
377
|
function requestBytes(ctx) {
|
|
321
378
|
if (typeof ctx.rawBody === "string") {
|
|
@@ -366,7 +423,30 @@ async function cancelThreadRuns(ctx, adapter, input) {
|
|
|
366
423
|
if (onFinish)
|
|
367
424
|
scheduleIngressWork(async () => {
|
|
368
425
|
try {
|
|
369
|
-
|
|
426
|
+
// A thread that waits on a decision still does after the cancel.
|
|
427
|
+
let waitsOn = {
|
|
428
|
+
interrupts: [],
|
|
429
|
+
};
|
|
430
|
+
try {
|
|
431
|
+
waitsOn = await httpIngressThreadWaitsOn({
|
|
432
|
+
userId: input.userId,
|
|
433
|
+
sourceThreadKey: input.sourceThreadKey,
|
|
434
|
+
sourceProvider: ctx.channel.kind,
|
|
435
|
+
threadId: input.threadId,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
logger.error(`channel thread could not be read (${logger.safeErrorLabel(error)})`);
|
|
440
|
+
}
|
|
441
|
+
await onFinish(immutableCopy(input.target), immutableCopy({
|
|
442
|
+
...input.delivery,
|
|
443
|
+
...(waitsOn.assistantId !== undefined && {
|
|
444
|
+
assistantId: waitsOn.assistantId,
|
|
445
|
+
}),
|
|
446
|
+
type: "skipped",
|
|
447
|
+
reason: "cancel",
|
|
448
|
+
interrupts: waitsOn.interrupts,
|
|
449
|
+
}));
|
|
370
450
|
}
|
|
371
451
|
catch (error) {
|
|
372
452
|
logger.error(`channel finish notice failed (${logger.safeErrorLabel(error)})`);
|
|
@@ -392,6 +472,9 @@ function validateCancelMessage(message) {
|
|
|
392
472
|
function validateInboundMessage(message, adapterInput) {
|
|
393
473
|
if (message.deliveryKey !== undefined)
|
|
394
474
|
nonblank(message.deliveryKey, "channel deliveryKey");
|
|
475
|
+
if (message.background !== undefined &&
|
|
476
|
+
typeof message.background !== "boolean")
|
|
477
|
+
throw new Error("channel background must be a boolean");
|
|
395
478
|
validateAddress(message);
|
|
396
479
|
// The adapter's `input` supplies what a message without content starts.
|
|
397
480
|
if (message.content !== undefined || !adapterInput)
|