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.
Files changed (51) hide show
  1. package/README.md +63 -32
  2. package/dist/channels/github/index.d.ts.map +1 -1
  3. package/dist/channels/github/index.js +5 -3
  4. package/dist/channels/github/index.js.map +1 -1
  5. package/dist/channels/github/mentions.d.ts +8 -3
  6. package/dist/channels/github/mentions.d.ts.map +1 -1
  7. package/dist/channels/github/mentions.js +113 -40
  8. package/dist/channels/github/mentions.js.map +1 -1
  9. package/dist/channels/github/schema.d.ts +150 -96
  10. package/dist/channels/github/schema.d.ts.map +1 -1
  11. package/dist/channels/github/schema.js +18 -5
  12. package/dist/channels/github/schema.js.map +1 -1
  13. package/dist/channels/github/types.d.ts +14 -10
  14. package/dist/channels/github/types.d.ts.map +1 -1
  15. package/dist/channels/http/endpoint.d.ts +28 -19
  16. package/dist/channels/http/endpoint.d.ts.map +1 -1
  17. package/dist/channels/http/endpoint.js +107 -37
  18. package/dist/channels/http/endpoint.js.map +1 -1
  19. package/dist/channels/http/http.d.ts.map +1 -1
  20. package/dist/channels/http/http.js +130 -47
  21. package/dist/channels/http/http.js.map +1 -1
  22. package/dist/channels/http/index.d.ts +1 -1
  23. package/dist/channels/http/index.d.ts.map +1 -1
  24. package/dist/channels/http/index.js.map +1 -1
  25. package/dist/channels/http/ingress.d.ts +17 -2
  26. package/dist/channels/http/ingress.d.ts.map +1 -1
  27. package/dist/channels/http/ingress.js +19 -5
  28. package/dist/channels/http/ingress.js.map +1 -1
  29. package/dist/channels/http/provider.d.ts +14 -11
  30. package/dist/channels/http/provider.d.ts.map +1 -1
  31. package/dist/channels/http/provider.js +19 -8
  32. package/dist/channels/http/provider.js.map +1 -1
  33. package/dist/channels/http/studio.d.ts +4 -3
  34. package/dist/channels/http/studio.d.ts.map +1 -1
  35. package/dist/channels/http/studio.js +9 -4
  36. package/dist/channels/http/studio.js.map +1 -1
  37. package/dist/channels/http/types.d.ts +57 -16
  38. package/dist/channels/http/types.d.ts.map +1 -1
  39. package/dist/channels/linear/index.d.ts.map +1 -1
  40. package/dist/channels/linear/index.js +10 -7
  41. package/dist/channels/linear/index.js.map +1 -1
  42. package/dist/channels/linear/types.d.ts +8 -6
  43. package/dist/channels/linear/types.d.ts.map +1 -1
  44. package/dist/channels/run-input.d.ts +9 -3
  45. package/dist/channels/run-input.d.ts.map +1 -1
  46. package/dist/channels/run-input.js +12 -7
  47. package/dist/channels/run-input.js.map +1 -1
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js.map +1 -1
  51. 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 an endpoint is sent. */
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
- * The fields a channel adds to the event it sends an endpoint, beside its key,
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`. For a union
38
- * of events, the payload is typed by the key.
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, "key" | "deliveryId" | "payload" | X> : never;
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 channel's own
75
- * event `fields` beside the payload and a `Request`'s headers,
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. Any other answer, a
79
- * redirect, or no answer within 60 seconds fails the handler, as a handler
80
- * that throws does. The URL is http(s), without credentials in it.
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?: EventFields<E>): (event: E, thread: EndpointThread) => Promise<ChannelRunResult | null>;
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;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAQ7C;;;;;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,4DAA4D;AAC5D,KAAK,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1E;;;GAGG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAErE;;;;GAIG;AACH,MAAM,MAAM,aAAa,CACvB,CAAC,SAAS,YAAY,GAAG,YAAY,EACrC,CAAC,SAAS,MAAM,CAAC,GAAG,KAAK,IACvB,CAAC,SAAS,OAAO,GAAG,IAAI,CAAC,CAAC,EAAE,KAAK,GAAG,YAAY,GAAG,SAAS,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;AAE9E,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;;;;;kBAQhB,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAE1D,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAMtE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EACnE,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAgDxE"}
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
- input: jsonValueSchema,
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 channel's own
92
- * event `fields` beside the payload and a `Request`'s headers,
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. Any other answer, a
96
- * redirect, or no answer within 60 seconds fails the handler, as a handler
97
- * that throws does. The URL is http(s), without credentials in it.
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
- return async (event, thread) => {
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 headers = new Headers(baseHeaders);
122
- const signature = signatureHeaders(signingKeys(), `msg_${event.deliveryId}`, Math.floor(Date.now() / 1000), body);
123
- for (const [name, value] of Object.entries(signature))
124
- headers.set(name, value);
125
- const response = await fetch(target, {
126
- method: "POST",
127
- headers,
128
- body,
129
- redirect: "manual",
130
- signal: AbortSignal.timeout(TIMEOUT_MS),
131
- });
132
- if (response.status === 204) {
133
- await response.body?.cancel();
134
- return null;
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 !== "https:" && parsed.protocol !== "http:") ||
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 http(s) URL, without credentials in it`);
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 (Object.keys(options).length ? [input, options] : input);
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":"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"}
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":"AAWA,OAAO,KAAK,EACV,kBAAkB,EAOnB,MAAM,YAAY,CAAC;AAYpB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAwCrE,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,oBAAoB,EACzB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CA8UnB"}
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
- return normalizeRunInput(await inputHook?.(immutableCopy(message), rawEvent === undefined ? undefined : structuredClone(rawEvent)));
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
- async function finish(done) {
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(done.assistantId ?? assistantId),
196
- ...(done.run && { run: done.run }),
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
- ...(outcome.assistantId !== undefined && {
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
- return finish(skipped.skipped === "paused"
297
- ? {
298
- interrupts: skipped.interrupts,
299
- ...(skipped.assistantId !== undefined && {
300
- assistantId: skipped.assistantId,
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
- if ("skipped" in outcome) {
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 started = outcome;
316
- scheduleIngressWork(() => deliver(started), { label });
317
- return (result.response ??
318
- Response.json({ status: "accepted", deliveryId }, { status: 202 }));
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
- await onFinish(immutableCopy(input.target), immutableCopy({ ...input.delivery, interrupts: [] }));
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)