eve 0.30.7 → 0.31.0

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 (240) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +5 -6
  3. package/dist/src/acp/adapter.d.ts +10 -4
  4. package/dist/src/acp/adapter.js +1 -1
  5. package/dist/src/channel/adapter-context.d.ts +1 -1
  6. package/dist/src/channel/adapter.d.ts +2 -4
  7. package/dist/src/channel/adapter.js +1 -1
  8. package/dist/src/channel/channel-address.d.ts +58 -0
  9. package/dist/src/channel/channel-address.js +1 -0
  10. package/dist/src/channel/channel-operations.d.ts +72 -0
  11. package/dist/src/channel/channel-operations.js +1 -0
  12. package/dist/src/channel/compiled-channel.d.ts +10 -6
  13. package/dist/src/channel/cross-channel-receive.d.ts +13 -25
  14. package/dist/src/channel/cross-channel-receive.js +2 -2
  15. package/dist/src/channel/receive-target.d.ts +1 -2
  16. package/dist/src/channel/routes.d.ts +21 -149
  17. package/dist/src/channel/schedule.d.ts +1 -1
  18. package/dist/src/channel/schedule.js +1 -1
  19. package/dist/src/channel/send-input.d.ts +6 -0
  20. package/dist/src/channel/send-input.js +1 -0
  21. package/dist/src/channel/session.d.ts +44 -30
  22. package/dist/src/channel/session.js +1 -1
  23. package/dist/src/channel/types.d.ts +8 -35
  24. package/dist/src/chunks/{use-eve-agent-k-XHfrJT.js → use-eve-agent-CF5D6jSV.js} +361 -367
  25. package/dist/src/chunks/{use-eve-agent-BXMIf5fR.js → use-eve-agent-DbYZLG0h.js} +364 -368
  26. package/dist/src/cli/commands/agent-prompt/build-and-verify.md +1 -2
  27. package/dist/src/cli/commands/channel-add-conflicts.js +1 -1
  28. package/dist/src/cli/commands/info.d.ts +1 -1
  29. package/dist/src/cli/commands/info.js +1 -1
  30. package/dist/src/cli/commands/registry-metadata.d.ts +15 -0
  31. package/dist/src/cli/commands/registry-metadata.js +1 -0
  32. package/dist/src/cli/commands/registry-package.d.ts +46 -0
  33. package/dist/src/cli/commands/registry-package.js +1 -0
  34. package/dist/src/cli/commands/registry.d.ts +1 -1
  35. package/dist/src/cli/commands/registry.js +4 -4
  36. package/dist/src/cli/dev/tui/runner.d.ts +1 -1
  37. package/dist/src/cli/dev/tui/runner.js +1 -1
  38. package/dist/src/cli/dev/tui/subagent-pump.js +1 -1
  39. package/dist/src/cli/dev/tui/terminal-renderer.js +2 -2
  40. package/dist/src/cli/dev/tui/tui.js +1 -1
  41. package/dist/src/cli/invoke/invoke.d.ts +1 -1
  42. package/dist/src/cli/invoke/invoke.js +1 -1
  43. package/dist/src/cli/invoke/result.d.ts +0 -11
  44. package/dist/src/cli/invoke/result.js +1 -1
  45. package/dist/src/client/client-error.d.ts +2 -0
  46. package/dist/src/client/client-error.js +1 -1
  47. package/dist/src/client/client.d.ts +7 -14
  48. package/dist/src/client/client.js +1 -1
  49. package/dist/src/client/eve-agent-store.d.ts +4 -4
  50. package/dist/src/client/eve-agent-store.js +1 -1
  51. package/dist/src/client/index.d.ts +3 -2
  52. package/dist/src/client/index.js +1 -1
  53. package/dist/src/client/message-reducer-types.d.ts +3 -0
  54. package/dist/src/client/message-reducer.js +1 -1
  55. package/dist/src/client/message-response.d.ts +2 -7
  56. package/dist/src/client/message-response.js +1 -1
  57. package/dist/src/client/open-stream.js +1 -1
  58. package/dist/src/client/session-controls.d.ts +29 -0
  59. package/dist/src/client/session-controls.js +1 -0
  60. package/dist/src/client/session-utils.d.ts +0 -20
  61. package/dist/src/client/session-utils.js +1 -1
  62. package/dist/src/client/session.d.ts +26 -90
  63. package/dist/src/client/session.js +1 -1
  64. package/dist/src/client/sessions.d.ts +20 -0
  65. package/dist/src/client/sessions.js +1 -0
  66. package/dist/src/client/types.d.ts +26 -47
  67. package/dist/src/compiler/extension-compatibility.d.ts +10 -10
  68. package/dist/src/compiler/extension-compatibility.js +1 -1
  69. package/dist/src/compiler/normalize-subagent.js +1 -1
  70. package/dist/src/evals/cli/eval-client.js +1 -1
  71. package/dist/src/evals/context.js +1 -1
  72. package/dist/src/evals/session.d.ts +7 -6
  73. package/dist/src/evals/session.js +1 -1
  74. package/dist/src/evals/types.d.ts +9 -9
  75. package/dist/src/execution/agent-handle-dispatch.js +1 -1
  76. package/dist/src/execution/cancel-descendant-turns-step.js +1 -1
  77. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  78. package/dist/src/execution/remote-agent-dispatch.d.ts +1 -3
  79. package/dist/src/execution/remote-agent-dispatch.js +1 -1
  80. package/dist/src/execution/runtime-context.js +1 -1
  81. package/dist/src/execution/runtime-errors.d.ts +1 -1
  82. package/dist/src/execution/session-command-token.d.ts +2 -0
  83. package/dist/src/execution/session-command-token.js +1 -1
  84. package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
  85. package/dist/src/execution/subagent-hitl-proxy.js +1 -1
  86. package/dist/src/execution/tool-auth.d.ts +1 -1
  87. package/dist/src/execution/tool-auth.js +1 -1
  88. package/dist/src/execution/turn-control-receiver.d.ts +2 -2
  89. package/dist/src/execution/turn-workflow.js +1 -1
  90. package/dist/src/execution/workflow-entry.js +1 -1
  91. package/dist/src/execution/workflow-runtime.js +1 -1
  92. package/dist/src/harness/cancelled-turn-emission.d.ts +1 -1
  93. package/dist/src/harness/cancelled-turn-emission.js +1 -1
  94. package/dist/src/harness/emission-state.d.ts +25 -0
  95. package/dist/src/harness/emission-state.js +1 -0
  96. package/dist/src/harness/emission.d.ts +5 -37
  97. package/dist/src/harness/emission.js +1 -1
  98. package/dist/src/harness/handles/prompt.d.ts +22 -3
  99. package/dist/src/harness/handles/prompt.js +1 -1
  100. package/dist/src/harness/handles/store.d.ts +0 -5
  101. package/dist/src/harness/handles/store.js +1 -1
  102. package/dist/src/harness/ordered-stream-emitter.d.ts +3 -2
  103. package/dist/src/harness/ordered-stream-emitter.js +1 -1
  104. package/dist/src/harness/runtime-actions.js +1 -1
  105. package/dist/src/harness/session-limit-enforcement.js +1 -1
  106. package/dist/src/harness/tool-loop.js +1 -1
  107. package/dist/src/harness/tools.d.ts +1 -1
  108. package/dist/src/harness/tools.js +1 -1
  109. package/dist/src/internal/application/package.js +1 -1
  110. package/dist/src/internal/nitro/dev-runtime-artifacts.js +1 -1
  111. package/dist/src/internal/nitro/dev-runtime-source-snapshot-copy.js +1 -1
  112. package/dist/src/internal/nitro/dev-runtime-source-snapshot-local-roots.d.ts +15 -0
  113. package/dist/src/internal/nitro/dev-runtime-source-snapshot-local-roots.js +1 -0
  114. package/dist/src/internal/nitro/dev-runtime-source-snapshot.d.ts +14 -2
  115. package/dist/src/internal/nitro/dev-runtime-source-snapshot.js +1 -1
  116. package/dist/src/internal/nitro/host/dev-server-http.js +1 -1
  117. package/dist/src/internal/nitro/routes/channel-dispatch.d.ts +2 -5
  118. package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
  119. package/dist/src/internal/nitro/routes/channel-route-context.d.ts +4 -3
  120. package/dist/src/internal/nitro/routes/channel-route-context.js +1 -1
  121. package/dist/src/packages/eve-catalog/src/index.js +1 -1
  122. package/dist/src/protocol/cancel-turn.d.ts +9 -4
  123. package/dist/src/protocol/cancel-turn.js +1 -1
  124. package/dist/src/protocol/message.d.ts +27 -17
  125. package/dist/src/protocol/message.js +2 -2
  126. package/dist/src/protocol/reset-session.d.ts +1 -1
  127. package/dist/src/protocol/routes.d.ts +27 -47
  128. package/dist/src/protocol/routes.js +1 -1
  129. package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +19 -21
  130. package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
  131. package/dist/src/public/channels/discord/discordChannel.d.ts +5 -4
  132. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  133. package/dist/src/public/channels/eve.d.ts +11 -10
  134. package/dist/src/public/channels/eve.js +1 -1
  135. package/dist/src/public/channels/github/dispatch.d.ts +8 -8
  136. package/dist/src/public/channels/github/dispatch.js +1 -1
  137. package/dist/src/public/channels/github/githubChannel.d.ts +4 -3
  138. package/dist/src/public/channels/github/githubChannel.js +1 -1
  139. package/dist/src/public/channels/github/pr-context.d.ts +1 -1
  140. package/dist/src/public/channels/index.d.ts +1 -1
  141. package/dist/src/public/channels/linear/linearChannel.d.ts +4 -3
  142. package/dist/src/public/channels/linear/linearChannel.js +1 -1
  143. package/dist/src/public/channels/slack/index.d.ts +1 -1
  144. package/dist/src/public/channels/slack/interactions.d.ts +3 -4
  145. package/dist/src/public/channels/slack/interactions.js +1 -1
  146. package/dist/src/public/channels/slack/session-operations.d.ts +37 -0
  147. package/dist/src/public/channels/slack/session-operations.js +1 -0
  148. package/dist/src/public/channels/slack/slackChannel.d.ts +56 -74
  149. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  150. package/dist/src/public/channels/teams/teamsChannel.d.ts +4 -3
  151. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  152. package/dist/src/public/channels/telegram/telegramChannel.d.ts +5 -4
  153. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  154. package/dist/src/public/channels/twilio/twilioChannel.d.ts +4 -3
  155. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  156. package/dist/src/public/definitions/callback-context.d.ts +1 -2
  157. package/dist/src/public/definitions/channel.d.ts +22 -86
  158. package/dist/src/public/definitions/channel.js +1 -1
  159. package/dist/src/public/definitions/hook.d.ts +1 -0
  160. package/dist/src/public/definitions/remote-agent.js +1 -1
  161. package/dist/src/public/definitions/schedule.d.ts +16 -16
  162. package/dist/src/public/definitions/tool.d.ts +5 -5
  163. package/dist/src/public/schedules/index.d.ts +1 -1
  164. package/dist/src/react/use-eve-agent.d.ts +7 -4
  165. package/dist/src/react/use-eve-agent.js +1 -1
  166. package/dist/src/runtime/agent/bootstrap-model-utils.d.ts +11 -0
  167. package/dist/src/runtime/agent/bootstrap-model-utils.js +1 -1
  168. package/dist/src/runtime/agent/mock-model-adapter.js +1 -1
  169. package/dist/src/runtime/agent/mock-model-fixtures.js +2 -2
  170. package/dist/src/runtime/prompt/compose.js +1 -1
  171. package/dist/src/runtime/subagents/dynamic-remote-agent-config.js +1 -1
  172. package/dist/src/runtime/types.d.ts +2 -2
  173. package/dist/src/services/inspect-application.d.ts +1 -1
  174. package/dist/src/services/inspect-application.js +1 -1
  175. package/dist/src/setup/channel-add-conflicts.js +1 -1
  176. package/dist/src/setup/integrations/linear/setup.js +1 -1
  177. package/dist/src/setup/scaffold/create/project.js +1 -1
  178. package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
  179. package/dist/src/setup/scaffold/create/web-template.js +29 -17
  180. package/dist/src/shared/async-iterable.d.ts +2 -0
  181. package/dist/src/shared/async-iterable.js +1 -0
  182. package/dist/src/shared/channel-definition.d.ts +7 -7
  183. package/dist/src/shared/schedule-definition.d.ts +2 -2
  184. package/dist/src/shared/tool-definition.d.ts +1 -1
  185. package/dist/src/svelte/index.js +1 -1
  186. package/dist/src/svelte/use-eve-agent.d.ts +8 -5
  187. package/dist/src/svelte/use-eve-agent.js +1 -1
  188. package/dist/src/vue/index.js +1 -1
  189. package/dist/src/vue/use-eve-agent.d.ts +8 -5
  190. package/dist/src/vue/use-eve-agent.js +1 -1
  191. package/docs/README.md +6 -6
  192. package/docs/channels/chat-sdk.mdx +5 -6
  193. package/docs/channels/custom.mdx +116 -144
  194. package/docs/channels/discord.mdx +1 -1
  195. package/docs/channels/eve.mdx +76 -30
  196. package/docs/channels/github.mdx +1 -1
  197. package/docs/channels/linear.mdx +10 -10
  198. package/docs/channels/overview.mdx +2 -2
  199. package/docs/channels/slack.mdx +46 -15
  200. package/docs/channels/telegram.mdx +1 -1
  201. package/docs/channels/twilio.mdx +1 -1
  202. package/docs/concepts/default-harness.md +11 -3
  203. package/docs/concepts/execution-model-and-durability.mdx +17 -5
  204. package/docs/concepts/sessions-runs-and-streaming.md +45 -16
  205. package/docs/evals/cases.mdx +3 -3
  206. package/docs/evals/targets.mdx +1 -1
  207. package/docs/guides/auth-and-route-protection.md +2 -1
  208. package/docs/guides/client/continuations.mdx +50 -77
  209. package/docs/guides/client/messages.mdx +29 -33
  210. package/docs/guides/client/output-schema.mdx +7 -14
  211. package/docs/guides/client/overview.mdx +40 -11
  212. package/docs/guides/client/streaming.mdx +18 -23
  213. package/docs/guides/frontend/nuxt.mdx +1 -1
  214. package/docs/guides/frontend/overview.mdx +40 -36
  215. package/docs/guides/frontend/sveltekit.mdx +1 -1
  216. package/docs/guides/frontend/use-eve-agent-svelte.mdx +19 -23
  217. package/docs/guides/frontend/use-eve-agent-vue.mdx +21 -25
  218. package/docs/guides/hooks.md +1 -1
  219. package/docs/guides/instrumentation.md +1 -1
  220. package/docs/install-integrations.mdx +4 -2
  221. package/docs/patterns/dynamic-scheduling.md +16 -15
  222. package/docs/reference/cli.md +4 -1
  223. package/docs/schedules.mdx +10 -11
  224. package/docs/subagents.mdx +1 -1
  225. package/docs/tools/human-in-the-loop.md +1 -1
  226. package/docs/tools/overview.mdx +26 -2
  227. package/docs/tutorial/ship-it.mdx +1 -1
  228. package/package.json +1 -1
  229. package/dist/src/channel/cancel.d.ts +0 -6
  230. package/dist/src/channel/cancel.js +0 -1
  231. package/dist/src/channel/clear-session.d.ts +0 -4
  232. package/dist/src/channel/clear-session.js +0 -1
  233. package/dist/src/channel/compact-session.d.ts +0 -4
  234. package/dist/src/channel/compact-session.js +0 -1
  235. package/dist/src/channel/reset-session.d.ts +0 -6
  236. package/dist/src/channel/reset-session.js +0 -1
  237. package/dist/src/channel/resolve-active-session.d.ts +0 -4
  238. package/dist/src/channel/resolve-active-session.js +0 -1
  239. package/dist/src/channel/send.d.ts +0 -6
  240. package/dist/src/channel/send.js +0 -1
@@ -1,16 +1,19 @@
1
- import type { CrossChannelReceiveOptions } from "#channel/cross-channel-receive.js";
1
+ import type { UserContent } from "ai";
2
+ import type { ChannelResolveSession, ChannelSource } from "#channel/channel-operations.js";
2
3
  import type { Session } from "#channel/session.js";
3
- import type { CancelTurnResult, SessionAuthContext } from "#channel/types.js";
4
+ import type { SessionAuthContext } from "#channel/types.js";
4
5
  import type { CardElement } from "#compiled/chat/index.js";
5
6
  import type { SessionContext } from "#public/definitions/callback-context.js";
6
- import type { ChannelSessionOps } from "#public/definitions/channel.js";
7
+ import type { ChannelContinuationOps } from "#public/definitions/channel.js";
7
8
  import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
8
9
  import { type SlackBotToken, type SlackHandle, type SlackThread, type SlackWorkspaceHandle } from "#public/channels/slack/api.js";
9
10
  import { type SlackEvent, type SlackEventEnvelope, type SlackMessage } from "#public/channels/slack/inbound.js";
10
11
  import { type LoadThreadContextMessagesOptions } from "#public/channels/slack/thread.js";
12
+ import { type SlackSessionOperations } from "#public/channels/slack/session-operations.js";
11
13
  import { type UploadPolicyInput } from "#public/channels/upload-policy.js";
12
14
  import { type SlackWebhookVerifier } from "#public/channels/slack/verify.js";
13
- import { type Channel, type ResetResult } from "#public/definitions/channel.js";
15
+ import { type Channel } from "#public/definitions/channel.js";
16
+ export type { SlackRespondOptions, SlackSendOptions, SlackSessionOperations, } from "#public/channels/slack/session-operations.js";
14
17
  type EventData<T extends UnstampedMessageStreamEvent["type"]> = Extract<UnstampedMessageStreamEvent, {
15
18
  type: T;
16
19
  }> extends {
@@ -39,10 +42,10 @@ export interface SlackChannelContext extends SlackContext {
39
42
  /**
40
43
  * Slack context handed to `events[type]` handlers. Extends
41
44
  * {@link SlackChannelContext} (`thread`, `slack`, hydrated `state`) with
42
- * session operations ({@link ChannelSessionOps}). Unlike the pre-dispatch
45
+ * continuation routing ({@link ChannelContinuationOps}). Unlike the pre-dispatch
43
46
  * {@link SlackContext}, `state` is hydrated here.
44
47
  */
45
- export interface SlackEventContext extends SlackChannelContext, ChannelSessionOps {
48
+ export interface SlackEventContext extends SlackChannelContext, ChannelContinuationOps {
46
49
  }
47
50
  export type { SlackApiResponse, SlackBotToken, SlackHandle, SlackThread, SlackWorkspaceHandle, } from "#public/channels/slack/api.js";
48
51
  export type { SlackWebhookVerifier } from "#public/channels/slack/verify.js";
@@ -162,7 +165,7 @@ export interface SlackChannelCredentials {
162
165
  */
163
166
  readonly webhookVerifier?: SlackWebhookVerifier;
164
167
  }
165
- /** Target accepted by `receive(slack, { target })` for proactive sessions. */
168
+ /** Target accepted by `ctx.to(slack, target)` from route and schedule handlers. */
166
169
  export interface SlackReceiveTarget {
167
170
  readonly channelId: string;
168
171
  readonly threadTs?: string;
@@ -184,16 +187,24 @@ export interface SlackInitialMessage {
184
187
  readonly fallbackText?: string;
185
188
  }
186
189
  /**
187
- * One imperative turn start requested by a generic Slack event handler.
188
- * The schedule API's `receive(slack, options)` payload with the Slack
189
- * channel already bound by the inbound webhook.
190
+ * Options for one turn requested by a generic Slack event handler.
190
191
  */
191
- export type SlackEventReceiveOptions = CrossChannelReceiveOptions<SlackReceiveTarget>;
192
+ export interface SlackEventSendOptions {
193
+ readonly auth: SessionAuthContext | null;
194
+ readonly target: SlackReceiveTarget;
195
+ }
196
+ /** Options for answering pending input requests from a generic Slack event handler. */
197
+ export interface SlackEventRespondOptions {
198
+ readonly auth: SessionAuthContext | null;
199
+ readonly target: SlackSessionTarget;
200
+ }
192
201
  /**
193
202
  * Starts a session on the current Slack channel from `onEvent`. Call it zero,
194
203
  * one, or many times; each invocation returns the resulting session.
195
204
  */
196
- export type SlackEventReceiveFn = (options: SlackEventReceiveOptions) => Promise<Session>;
205
+ export type SlackEventSendFn = (message: string | UserContent, options: SlackEventSendOptions) => Promise<Session>;
206
+ /** Answers pending input requests on one Slack thread. */
207
+ export type SlackEventRespondFn = (inputResponses: Parameters<SlackSessionOperations["respond"]>[0], options: SlackEventRespondOptions) => ReturnType<SlackSessionOperations["respond"]>;
197
208
  /**
198
209
  * Slack thread identity used by workspace-scoped inbound helpers.
199
210
  */
@@ -201,52 +212,41 @@ export interface SlackSessionTarget {
201
212
  readonly channelId: string;
202
213
  readonly threadTs: string;
203
214
  }
204
- /**
205
- * Options for cancelling the turn bound to a message or interaction context.
206
- * `turnId` guards against a stale request cancelling a newer turn.
207
- */
208
- export interface SlackCancelOptions {
209
- readonly turnId?: string;
210
- }
211
- /** Options for resetting the session bound to a message or interaction context. */
212
- export interface SlackResetOptions {
213
- /** Human-readable terminal reason. Do not include credentials or message contents. */
214
- readonly reason?: string;
215
- }
216
- /**
217
- * Target and optional stale-turn guard accepted by `onEvent`'s cancellation
218
- * helper.
219
- */
220
- export interface SlackEventCancelOptions extends SlackSessionTarget, SlackCancelOptions {
221
- }
222
- /** Target and optional terminal reason accepted by `onEvent`'s reset helper. */
223
- export interface SlackEventResetOptions extends SlackSessionTarget, SlackResetOptions {
224
- }
225
215
  /**
226
216
  * Imperative surface handed to `slackChannel({ onEvent })`. Generic Events API
227
217
  * payloads are not necessarily tied to one thread, so the context exposes a
228
- * workspace API handle plus a Slack-bound `receive` function rather than the
218
+ * workspace API handle plus Slack-bound operations rather than the
229
219
  * thread-scoped {@link SlackContext} used by message handlers.
230
220
  */
231
221
  export interface SlackInboundEventContext {
232
- /**
233
- * Cancels the active turn for a Slack thread. Both `"accepted"` and
234
- * `"no_active_turn"` are successful outcomes.
235
- */
236
- readonly cancel: (options: SlackEventCancelOptions) => Promise<CancelTurnResult>;
222
+ /** Starts a turn on this Slack channel using the proactive send contract. */
223
+ readonly send: SlackEventSendFn;
224
+ /** Answers pending input requests on one Slack thread. */
225
+ readonly respond: SlackEventRespondFn;
226
+ /** Cancels the active turn for one Slack thread. */
227
+ cancel(input: {
228
+ readonly target: SlackSessionTarget;
229
+ readonly turnId?: string;
230
+ }): ReturnType<ChannelSource<SlackChannelState>["cancel"]>;
231
+ /** Compacts the current session for one Slack thread. */
232
+ compact(input: {
233
+ readonly target: SlackSessionTarget;
234
+ }): ReturnType<ChannelSource<SlackChannelState>["compact"]>;
235
+ /** Clears the current session for one Slack thread. */
236
+ clear(input: {
237
+ readonly target: SlackSessionTarget;
238
+ }): ReturnType<ChannelSource<SlackChannelState>["clear"]>;
239
+ /** Resets the current session for one Slack thread. */
240
+ reset(input: {
241
+ readonly reason?: string;
242
+ readonly target: SlackSessionTarget;
243
+ }): ReturnType<ChannelSource<SlackChannelState>["reset"]>;
244
+ /** Resolves the current session for one Slack thread. */
245
+ resolveSession(input: {
246
+ readonly target: SlackSessionTarget;
247
+ }): ReturnType<ChannelResolveSession>;
237
248
  /** The complete signed Events API callback envelope. */
238
249
  readonly envelope: SlackEventEnvelope;
239
- /** Starts a turn on this Slack channel using the proactive receive contract. */
240
- readonly receive: SlackEventReceiveFn;
241
- /** Resolves the active eve session for a Slack channel thread. */
242
- readonly resolveActiveSession: (target: SlackSessionTarget) => Promise<{
243
- readonly sessionId: string;
244
- } | undefined>;
245
- /**
246
- * Terminally retires the session for a Slack thread so its next message starts
247
- * a fresh session.
248
- */
249
- readonly reset: (options: SlackEventResetOptions) => Promise<ResetResult>;
250
250
  /** Workspace-scoped Slack identity and raw Web API escape hatch. */
251
251
  readonly slack: SlackWorkspaceHandle;
252
252
  /** Keeps detached handler work alive after the Slack webhook is acknowledged. */
@@ -256,34 +256,14 @@ export interface SlackInboundEventContext {
256
256
  * Message-scoped context handed to `onMessage`, `onAppMention`, and
257
257
  * `onDirectMessage`.
258
258
  */
259
- export interface SlackInboundMessageContext extends SlackContext {
260
- /**
261
- * Cancels the active turn in this message's thread. Both `"accepted"` and
262
- * `"no_active_turn"` are successful outcomes.
263
- */
264
- cancel(options?: SlackCancelOptions): Promise<CancelTurnResult>;
259
+ export interface SlackInboundMessageContext extends SlackContext, SlackSessionOperations {
265
260
  /** Returns whether this message belongs to a thread with an active eve session. */
266
261
  isSubscribed(): Promise<boolean>;
267
262
  /** Returns whether the inbound event explicitly mentions this bot. */
268
263
  isBotMentioned(): boolean;
269
- /**
270
- * Terminally retires this thread's session so its next delivered message
271
- * starts with fresh history and state.
272
- */
273
- reset(options?: SlackResetOptions): Promise<ResetResult>;
274
264
  }
275
265
  /** Interaction-scoped context handed to `slackChannel({ onInteraction })`. */
276
- export interface SlackInteractionContext extends SlackContext {
277
- /**
278
- * Cancels the active turn in the interaction's thread. Both `"accepted"` and
279
- * `"no_active_turn"` are successful outcomes.
280
- */
281
- cancel(options?: SlackCancelOptions): Promise<CancelTurnResult>;
282
- /**
283
- * Terminally retires this thread's session so the continuation token can
284
- * start a fresh session.
285
- */
286
- reset(options?: SlackResetOptions): Promise<ResetResult>;
266
+ export interface SlackInteractionContext extends SlackContext, SlackSessionOperations {
287
267
  }
288
268
  export interface SlackInteractionAction {
289
269
  readonly actionId: string;
@@ -346,11 +326,13 @@ export type SlackInboundResultOrPromise = SlackMentionResultOrPromise;
346
326
  * `slackChannel({ events })`. Each key is optional; supplying one replaces
347
327
  * only that event's built-in default (see {@link defaultEvents}). Handlers
348
328
  * receive the event data, the {@link SlackEventContext}, and the session
349
- * {@link SessionContext}; `session.failed` receives only data and context.
329
+ * {@link SessionContext}; `session.failed` receives only data and channel
330
+ * context and exposes the ID as `data.sessionId`.
350
331
  */
351
332
  export interface SlackChannelEvents {
352
333
  readonly "turn.started"?: SlackEventHandler<"turn.started">;
353
334
  readonly "actions.requested"?: SlackEventHandler<"actions.requested">;
335
+ readonly "action.partial"?: SlackEventHandler<"action.partial">;
354
336
  readonly "action.result"?: SlackEventHandler<"action.result">;
355
337
  readonly "message.completed"?: SlackEventHandler<"message.completed">;
356
338
  readonly "message.appended"?: SlackEventHandler<"message.appended">;
@@ -449,7 +431,7 @@ export interface SlackChannelConfig {
449
431
  * neither a specialized handler nor `onEvent` is authored, mentions and DMs
450
432
  * retain their built-in defaults and other event types are ignored.
451
433
  *
452
- * The handler owns control flow. Call `ctx.receive(...)` zero, one, or many
434
+ * The handler owns control flow. Call `ctx.send(...)` zero, one, or many
453
435
  * times to start turns on Slack, and use `ctx.waitUntil(...)` for detached
454
436
  * work. The return value is ignored. Runs after the webhook has been
455
437
  * acknowledged through the host's `waitUntil` mechanism. Errors are caught
@@ -1 +1 @@
1
- import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,buildSlackWorkspaceHandle,slackContinuationToken}from"#public/channels/slack/api.js";import{slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{formatSlackInboundMessage,formatSlackThreadContext}from"#public/channels/slack/model-context.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest as verifySlackRequest$1}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=createSlackFetchFile({botToken:e.credentials?.botToken}),o=e.events?.[`authorization.required`],s=e.events?.[`turn.started`]??defaultEvents[`turn.started`],c={...defaultEvents,...e.events,async"turn.started"(e,t,n){let r=slackUserIdFromAuthContext(n.session.auth.current);r!==void 0&&(t.state.triggeringUserId=r),await s(e,t,n)},"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(),"authorization.required":o===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(o)},u=new Set;return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,lastReasoningTypingAtMs:null,lastReasoningTypingStatus:null,pendingAuthMessageTs:{}},fetchFile:n,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{cancel:r,reset:i,resolveActiveSession:a,send:o,waitUntil:s})=>{let c=await verifyInbound(n,e.credentials);return c===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(n.headers)?new Response(`ok`):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(c,{cancel:r,reset:i,send:o,waitUntil:s},{config:e}):handleEventPost({body:c,cancel:r,reset:i,send:o,resolveActiveSession:a,waitUntil:s,config:e,uploadPolicy:t,handledEvents:u,headers:n.headers})})],receive(t,{send:n}){return receiveOnSlack(t,{credentials:e.credentials,send:n})},events:c})}async function receiveOnSlack(e,t){let n=e.target,r=n.channelId;if(!r||typeof r!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let i=typeof n.threadTs==`string`?n.threadTs:``,a=n.initialMessage;if(a&&i.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let s=i;if(a){let{thread:e}=buildSlackBinding({botToken:t.credentials?.botToken,channelId:r,threadTs:``,teamId:t.teamId}),n={card:a.card};a.fallbackText!==void 0&&(n.fallbackText=a.fallbackText),s=(await e.post(n)).id}let l=s||crypto.randomUUID();return t.send(e.message,{auth:e.auth,continuationToken:slackContinuationToken(r,l),state:{channelId:r,threadTs:s||null,teamId:t.teamId??null,triggeringUserId:null}})}function constrainAuthorizationRequired(e){return(t,n,r)=>e(t,{postEphemeral:(e,t)=>n.thread.postEphemeral(e,t),postDirectMessage:(e,t)=>n.thread.postDirectMessage(e,t),state:n.state},r)}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(t){let{config:n}=t,r,i;try{r=parseSlackWebhookBody(t.body,{headers:t.headers}),i=parseSlackEventEnvelope(t.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(r.kind===`url_verification`)return new Response(r.challenge,{status:200,headers:{"content-type":`text/plain`}});if(i===null)return new Response(`ok`);let a=typeof i.api_app_id==`string`?i.api_app_id:void 0,o=slackEventBotUserId(i),dispatch=null,s=null;if(r.kind===`app_mention`||r.kind===`direct_message`){let e=r.kind,i=slackMessageFromWebhookPayload(r);if(i!==null&&!isSelfAuthoredSlackMessage({appId:a,botUserId:o},i)){let dispatchMessageWith=r=>()=>dispatchSlackMessage({appId:a,botUserId:o,cancel:t.cancel,credentials:n.credentials,handler:r,kind:e,message:i,resolveActiveSession:t.resolveActiveSession,reset:t.reset,send:t.send,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy}),r=(e===`app_mention`?n.onAppMention:n.onDirectMessage)??n.onMessage;r===void 0?s=dispatchMessageWith(e===`app_mention`?defaultOnAppMention:defaultOnDirectMessage):dispatch=()=>dispatchSlackMessage({appId:a,botUserId:o,cancel:t.cancel,credentials:n.credentials,handler:r,kind:e,message:i,resolveActiveSession:t.resolveActiveSession,reset:t.reset,send:t.send,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy})}}if(dispatch===null&&n.onMessage!==void 0){let e=parseMessageEvent(i);e!==null&&!isSelfAuthoredSlackMessage({appId:a,botUserId:o},e)&&(o===void 0||!e.text.includes(`<@${o}`))&&(dispatch=()=>dispatchSlackMessage({appId:a,botUserId:o,cancel:t.cancel,credentials:n.credentials,handler:n.onMessage,kind:`channel_message`,message:e,resolveActiveSession:t.resolveActiveSession,reset:t.reset,send:t.send,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy}))}let c=n.onEvent;if(dispatch===null&&c!==void 0&&(dispatch=()=>dispatchSlackEvent({cancel:t.cancel,credentials:n.credentials,envelope:i,handler:c,resolveActiveSession:t.resolveActiveSession,reset:t.reset,send:t.send})),dispatch??=s,dispatch===null)return new Response(`ok`);let l=i.event_id;if(l){if(t.handledEvents.has(l))return log.warn(`received a duplicate event`,{event_id:l,event_time:i.event_time,retry_num:r.retry?.num??`(null)`,retry_reason:r.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(l,t.handledEvents)}return t.waitUntil(dispatch()),new Response(`ok`)}function isSelfAuthoredSlackMessage(e,t){return e.botUserId!==void 0&&t.author?.userId===e.botUserId||e.appId!==void 0&&t.raw.app_id===e.appId}async function dispatchSlackMessage(e){let t=slackContinuationToken(e.message.channelId,e.message.threadTs),{thread:r,slack:i}=buildSlackBinding({appId:e.appId,botToken:e.credentials?.botToken,botUserId:e.botUserId,channelId:e.message.channelId,threadTs:e.message.threadTs,teamId:e.message.teamId}),a={cancel:(n={})=>e.cancel({continuationToken:t,turnId:n.turnId}),isBotMentioned:()=>e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),isSubscribed:async()=>await e.resolveActiveSession({continuationToken:t})!==void 0,reset:(n={})=>e.reset({continuationToken:t,reason:n.reason}),slack:i,thread:r},s;try{s=await e.handler(a,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId});return}s!=null&&await deliverSlackMessage({credentials:e.credentials,kind:e.kind,message:e.message,result:s,send:e.send,thread:r,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function dispatchSlackEvent(e){let t=e.envelope.event.team_id,r=typeof t==`string`?t:typeof e.envelope.team_id==`string`?e.envelope.team_id:void 0,i=[],a={cancel:({channelId:t,threadTs:n,turnId:r})=>e.cancel({continuationToken:slackContinuationToken(t,n),turnId:r}),envelope:e.envelope,receive:t=>receiveOnSlack(t,{credentials:e.credentials,send:e.send,teamId:r}),resolveActiveSession:({channelId:t,threadTs:n})=>e.resolveActiveSession({continuationToken:slackContinuationToken(t,n)}),reset:({channelId:t,threadTs:n,reason:r})=>e.reset({continuationToken:slackContinuationToken(t,n),reason:r}),slack:buildSlackWorkspaceHandle({botToken:e.credentials?.botToken,teamId:r}),waitUntil(e){i.push(e)}};try{await e.handler(a,e.envelope.event)}catch(t){logError(log,`event handler failed`,t,{eventId:e.envelope.event_id,eventType:e.envelope.event.type})}await Promise.allSettled(i)}async function verifyInbound(e,t){try{return await verifySlackRequest$1(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function deliverSlackMessage(e){let{message:t,thread:r}=e;try{let n=formatSlackThreadContext(e.threadContext===void 0?[]:await loadThreadContextMessages(r,t,e.threadContext)),i=await collectInboundFileParts({mention:t,thread:r,policy:e.uploadPolicy}),a={channelId:t.channelId,fullName:t.author?.fullName,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},o=formatSlackInboundMessage(a,t),s=buildSlackTurnMessage(n===void 0?o:`${n}\n\n${o}`,i),l=e.result.context??[];await e.send(l.length===0?{message:s}:{message:s,context:l},{auth:e.result.auth,continuationToken:slackContinuationToken(t.channelId,t.threadTs),state:{channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId??null,triggeringUserId:a.userId||null},title:t.markdown})}catch(r){logError(log,`${e.kind} delivery failed`,r,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};
1
+ import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,buildSlackWorkspaceHandle,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{bindSlackSessionOperations}from"#public/channels/slack/session-operations.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{formatSlackInboundMessage,formatSlackThreadContext}from"#public/channels/slack/model-context.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest as verifySlackRequest$1}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.continuation?.rekey(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=createSlackFetchFile({botToken:e.credentials?.botToken}),o=e.events?.[`authorization.required`],s=e.events?.[`turn.started`]??defaultEvents[`turn.started`],c={...defaultEvents,...e.events,async"turn.started"(e,t,n){let r=slackUserIdFromAuthContext(n.session.auth.current);r!==void 0&&(t.state.triggeringUserId=r),await s(e,t,n)},"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(),"authorization.required":o===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(o)},l=new Set;return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,lastReasoningTypingAtMs:null,lastReasoningTypingStatus:null,pendingAuthMessageTs:{}},fetchFile:n,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,r)=>{let{waitUntil:i}=r,{from:a,resolveSession:o}=r,s=await verifyInbound(n,e.credentials);return s===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(n.headers)?new Response(`ok`):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(s,{from:a,resolveSession:o,waitUntil:i},{config:e}):handleEventPost({body:s,from:a,resolveSession:o,waitUntil:i,config:e,uploadPolicy:t,handledEvents:l,headers:n.headers})})],receive(t,{from:n}){return receiveOnSlack(t,{from:n,credentials:e.credentials})},events:c})}async function receiveOnSlack(e,t){let n=e.target,r=n.channelId;if(!r||typeof r!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let i=typeof n.threadTs==`string`?n.threadTs:``,a=n.initialMessage;if(a&&i.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let s=i;if(a){let{thread:e}=buildSlackBinding({botToken:t.credentials?.botToken,channelId:r,threadTs:``,teamId:t.teamId}),n={card:a.card};a.fallbackText!==void 0&&(n.fallbackText=a.fallbackText),s=(await e.post(n)).id}let l=s||crypto.randomUUID();return t.from(slackContinuationToken(r,l)).send(e.message,{auth:e.auth,state:{channelId:r,threadTs:s||null,teamId:t.teamId??null,triggeringUserId:t.triggeringUserId??null}})}function constrainAuthorizationRequired(e){return(t,n,r)=>e(t,{postEphemeral:(e,t)=>n.thread.postEphemeral(e,t),postDirectMessage:(e,t)=>n.thread.postDirectMessage(e,t),state:n.state},r)}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(t){let{config:n}=t,r,i;try{r=parseSlackWebhookBody(t.body,{headers:t.headers}),i=parseSlackEventEnvelope(t.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(r.kind===`url_verification`)return new Response(r.challenge,{status:200,headers:{"content-type":`text/plain`}});if(i===null)return new Response(`ok`);let a=typeof i.api_app_id==`string`?i.api_app_id:void 0,o=slackEventBotUserId(i),dispatch=null,s=null;if(r.kind===`app_mention`||r.kind===`direct_message`){let e=r.kind,i=slackMessageFromWebhookPayload(r);if(i!==null&&!isSelfAuthoredSlackMessage({appId:a,botUserId:o},i)){let dispatchMessageWith=r=>()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:r,kind:e,message:i,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy}),r=(e===`app_mention`?n.onAppMention:n.onDirectMessage)??n.onMessage;r===void 0?s=dispatchMessageWith(e===`app_mention`?defaultOnAppMention:defaultOnDirectMessage):dispatch=()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:r,kind:e,message:i,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy})}}if(dispatch===null&&n.onMessage!==void 0){let e=parseMessageEvent(i);e!==null&&!isSelfAuthoredSlackMessage({appId:a,botUserId:o},e)&&(o===void 0||!e.text.includes(`<@${o}`))&&(dispatch=()=>dispatchSlackMessage({appId:a,botUserId:o,from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,handler:n.onMessage,kind:`channel_message`,message:e,threadContext:n.threadContext,uploadPolicy:t.uploadPolicy}))}let c=n.onEvent;if(dispatch===null&&c!==void 0&&(dispatch=()=>dispatchSlackEvent({from:t.from,resolveSession:t.resolveSession,credentials:n.credentials,envelope:i,handler:c})),dispatch??=s,dispatch===null)return new Response(`ok`);let l=i.event_id;if(l){if(t.handledEvents.has(l))return log.warn(`received a duplicate event`,{event_id:l,event_time:i.event_time,retry_num:r.retry?.num??`(null)`,retry_reason:r.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(l,t.handledEvents)}return t.waitUntil(dispatch()),new Response(`ok`)}function isSelfAuthoredSlackMessage(e,t){return e.botUserId!==void 0&&t.author?.userId===e.botUserId||e.appId!==void 0&&t.raw.app_id===e.appId}async function dispatchSlackMessage(e){let t=slackContinuationToken(e.message.channelId,e.message.threadTs),{thread:r,slack:i}=buildSlackBinding({appId:e.appId,botToken:e.credentials?.botToken,botUserId:e.botUserId,channelId:e.message.channelId,threadTs:e.message.threadTs,teamId:e.message.teamId}),a=e.message.author,s=bindSlackSessionOperations({address:t,defaultAuth:a===void 0?null:buildSlackAuthContext({channelId:e.message.channelId,fullName:a.fullName,isBot:a.isBot,teamId:e.message.teamId,threadTs:e.message.threadTs,userId:a.userId,userName:a.userName}),from:e.from,resolveSession:e.resolveSession,state:{channelId:e.message.channelId,teamId:e.message.teamId??null,threadTs:e.message.threadTs,triggeringUserId:a?.userId??null}}),u={...s,isBotMentioned:()=>e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),isSubscribed:async()=>await s.resolveSession()!==void 0,slack:i,thread:r},d;try{d=await e.handler(u,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId});return}d!=null&&await deliverSlackMessage({credentials:e.credentials,kind:e.kind,message:e.message,result:d,sessionOperations:s,thread:r,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function dispatchSlackEvent(e){let t=e.envelope.event.team_id,r=typeof t==`string`?t:typeof e.envelope.team_id==`string`?e.envelope.team_id:void 0,i=[],sourceFor=t=>e.from(slackContinuationToken(t.channelId,t.threadTs)),a={cancel:({target:e,turnId:t})=>sourceFor(e).cancel({turnId:t}),clear:({target:e})=>sourceFor(e).clear(),compact:({target:e})=>sourceFor(e).compact(),envelope:e.envelope,reset:({reason:e,target:t})=>sourceFor(t).reset({reason:e}),resolveSession:({target:t})=>e.resolveSession(slackContinuationToken(t.channelId,t.threadTs)),respond:(e,{auth:t,target:n})=>sourceFor(n).respond(e,{auth:t}),send:(t,{auth:n,target:i})=>receiveOnSlack({auth:n,message:t,target:i},{from:e.from,credentials:e.credentials,teamId:r,...typeof e.envelope.event.user==`string`?{triggeringUserId:e.envelope.event.user}:{}}),slack:buildSlackWorkspaceHandle({botToken:e.credentials?.botToken,teamId:r}),waitUntil(e){i.push(e)}};try{await e.handler(a,e.envelope.event)}catch(t){logError(log,`event handler failed`,t,{eventId:e.envelope.event_id,eventType:e.envelope.event.type})}await Promise.allSettled(i)}async function verifyInbound(e,t){try{return await verifySlackRequest$1(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function deliverSlackMessage(e){let{message:t,thread:r}=e;try{let n=formatSlackThreadContext(e.threadContext===void 0?[]:await loadThreadContextMessages(r,t,e.threadContext)),i=await collectInboundFileParts({mention:t,thread:r,policy:e.uploadPolicy}),a=formatSlackInboundMessage({channelId:t.channelId,fullName:t.author?.fullName,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},t),o=buildSlackTurnMessage(n===void 0?a:`${n}\n\n${a}`,i),s=e.result.context??[],c=s.length===0?{auth:e.result.auth,title:t.markdown}:{auth:e.result.auth,context:s,title:t.markdown};await e.sessionOperations.send(o,c)}catch(r){logError(log,`${e.kind} delivery failed`,r,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};
@@ -1,7 +1,7 @@
1
1
  import type { TeamsInstrumentationMetadata } from "#public/channels/teams/index.js";
2
2
  import type { SessionAuthContext } from "#channel/types.js";
3
3
  import type { SessionContext } from "#public/definitions/callback-context.js";
4
- import type { ChannelSessionOps } from "#public/definitions/channel.js";
4
+ import type { ChannelContinuationOps } from "#public/definitions/channel.js";
5
5
  import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
6
6
  import { type TeamsFilesConfig } from "#public/channels/teams/attachments.js";
7
7
  import { type TeamsApiOptions, type TeamsApiResponse, type TeamsChannelAccount, type TeamsCredentials, type TeamsMention, type TeamsMessageBody, type TeamsOutboundActivity, type TeamsPostedActivity } from "#public/channels/teams/api.js";
@@ -29,8 +29,8 @@ export interface TeamsChannelContext extends TeamsContext {
29
29
  readonly adaptiveCardVersion: string;
30
30
  state: TeamsChannelState;
31
31
  }
32
- /** Event-handler Teams context, including session operations. */
33
- export interface TeamsEventContext extends TeamsChannelContext, ChannelSessionOps {
32
+ /** Event-handler Teams context, including continuation routing. */
33
+ export interface TeamsEventContext extends TeamsChannelContext, ChannelContinuationOps {
34
34
  }
35
35
  /** JSON-serializable Teams channel state. */
36
36
  export interface TeamsChannelState {
@@ -98,6 +98,7 @@ type TeamsSessionFailedHandler = (data: EventData<"session.failed">, channel: Te
98
98
  export interface TeamsChannelEvents {
99
99
  readonly "turn.started"?: TeamsEventHandler<"turn.started">;
100
100
  readonly "actions.requested"?: TeamsEventHandler<"actions.requested">;
101
+ readonly "action.partial"?: TeamsEventHandler<"action.partial">;
101
102
  readonly "action.result"?: TeamsEventHandler<"action.result">;
102
103
  readonly "message.completed"?: TeamsEventHandler<"message.completed">;
103
104
  readonly "message.appended"?: TeamsEventHandler<"message.appended">;
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{readNonEmptyString}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{callTeamsConnectorApi,normalizeTeamsContinuationAddress,normalizeTeamsPostInput,replyToTeamsActivity,sendTeamsActivity,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{deriveTeamsInputResponses,isTeamsInputResponseActivity,readTeamsInputReplyToActivityId,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{formatTeamsContextBlock,parseTeamsActivity,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{defaultEvents,defaultOnMessage,defaultTeamsAuth,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),o=e.onMessage??defaultOnMessage,s=e.onInputResponse??(e.onMessage===void 0?defaultOnInputResponse:rejectInput),c={...defaultEvents,...e.events};return defineChannel({kindHint:`teams`,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t),metadata:e=>({channelId:e.channelId,conversationType:e.conversationType,teamId:e.teamId}),context(t,n){return rebuildTeamsContext(t,n,e)},routes:[POST(e.route??`/eve/v1/teams`,async(n,{resolveActiveSession:i,send:a,waitUntil:c})=>{let l=await verifyInbound(n,e.credentials);if(l===null)return new Response(`unauthorized`,{status:401});let u;try{u=parseJsonObject(JSON.parse(l))}catch(e){return log.warn(`inbound Teams body is not valid JSON`,{error:e}),teamsOk()}let d=parseTeamsActivity(u);return d===null?teamsOk():d.type===`message`?(c(isTeamsInputResponseActivity(d)?dispatchInputResponses({activity:d,config:e,onInputResponse:s,send:a}):dispatchMessage({activity:d,config:e,filesPolicy:t,onMessage:o,resolveActiveSession:i,send:a})),teamsOk()):d.type===`invoke`?handleInvoke({activity:d,config:e,onInputResponse:s,send:a,waitUntil:c}):teamsOk()})],async receive(t,{send:r}){let i=t.target,a=readNonEmptyString(i.serviceUrl),o=readNonEmptyString(i.conversationId);if(!a||!o)throw Error(`teamsChannel().receive requires target.serviceUrl and target.conversationId.`);let s=readNonEmptyString(i.conversationType)??null,c=readNonEmptyString(i.replyToActivityId)??null,l=i.initialMessage;if(l!==void 0&&c!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let u={...initialTeamsState(),channelId:readNonEmptyString(i.channelId)??null,conversationId:o,conversationType:s,replyToActivityId:c,serviceUrl:a,teamId:readNonEmptyString(i.teamId)??null,tenantId:readNonEmptyString(i.tenantId)??null};if(l!==void 0){let t=await buildTeamsBinding({config:e,state:u}).thread.post(l);s!==`personal`&&t.id&&(c=t.id,u.replyToActivityId=t.id)}return r(t.message,{auth:t.auth,continuationToken:teamsContinuationToken({conversationId:o,replyToActivityId:c,tenantId:u.tenantId}),state:u})},events:c})}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,state:e}}function buildTeamsBinding(e){let n=buildTeamsHandle(e);return{teams:n,thread:{mentionUser:teamsMentionUser,post(e){return n.sendActivity(e)},async startTyping(){try{await n.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,t){return n.updateActivity(e,t)}}}}function buildTeamsHandle(e){let t=e.state,n=e.config.api,r=e.config.credentials;function requireAddress(){let e=t.conversationId??``,n=t.serviceUrl??``;if(!e||!n)throw Error(`teamsChannel: missing serviceUrl or conversationId for outbound message.`);return{conversationId:e,serviceUrl:n}}function anchor(n){if(!n.id||t.replyToActivityId||t.conversationType===`personal`)return;t.replyToActivityId=n.id;let r=t.conversationId;r&&e.session?.setContinuationToken(teamsContinuationToken({conversationId:r,replyToActivityId:n.id,tenantId:t.tenantId}))}async function send(e){let i=requireAddress(),a=buildOutboundActivity(t,e),o=t.replyToActivityId===null?await sendTeamsActivity({...n,body:a,credentials:r,conversationId:i.conversationId,serviceUrl:i.serviceUrl}):await replyToTeamsActivity({...n,body:a,credentials:r,activityId:t.replyToActivityId,conversationId:i.conversationId,serviceUrl:i.serviceUrl});return anchor(o),o}return{channelId:t.channelId??void 0,conversationId:t.conversationId??``,conversationType:t.conversationType??void 0,replyToActivityId:t.replyToActivityId??void 0,serviceUrl:t.serviceUrl??``,teamId:t.teamId??void 0,tenantId:t.tenantId??void 0,request(e,t,i){let a=requireAddress();return callTeamsConnectorApi({...n,body:t,credentials:r,method:i?.method,path:e,serviceUrl:a.serviceUrl})},sendActivity:send,replyToActivity(e){let i=requireAddress(),a=t.replyToActivityId??``;if(!a)throw Error(`teamsChannel: missing reply activity id.`);return replyToTeamsActivity({...n,body:buildOutboundActivity(t,e),credentials:r,activityId:a,conversationId:i.conversationId,serviceUrl:i.serviceUrl})},updateActivity(e,i){let a=requireAddress();return updateTeamsActivity({...n,body:buildOutboundActivity(t,i),credentials:r,activityId:e,conversationId:a.conversationId,serviceUrl:a.serviceUrl})},async startTyping(){let e=requireAddress();await triggerTeamsTypingIndicator({...n,credentials:r,conversationId:e.conversationId,serviceUrl:e.serviceUrl})}}}async function verifyInbound(e,t){try{return await verifyTeamsRequest(e,{appId:t?.webhookVerifier?void 0:t?.appId,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`teams inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r=stateToken(t),i={...n,isSubscribed:async()=>await e.resolveActiveSession({continuationToken:r})!==void 0},a;try{a=await e.onMessage(i,e.activity)}catch(e){log.error(`Teams message handler failed`,{error:e});return}if(a==null)return;let o=collectTeamsFileParts(e.activity.attachments,e.filesPolicy),s=buildTeamsTurnMessage(e.activity.text,o),c={activityId:e.activity.id,channelId:e.activity.teamsChannelId,conversationId:e.activity.conversation.id,conversationType:e.activity.conversationType,scope:e.activity.scope,teamId:e.activity.teamId,tenantId:e.activity.tenantId,userId:e.activity.from.id,userName:e.activity.from.name},l=a.context??[];try{await e.send({message:s,context:[formatTeamsContextBlock(c),...l]},{auth:a.auth,continuationToken:r,state:t})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){if(isTeamsInputResponseActivity(e.activity))return e.waitUntil(dispatchInputResponses({activity:e.activity,config:e.config,onInputResponse:e.onInputResponse,send:e.send})),Response.json(teamsInvokeResponse());if(e.config.onInvoke===void 0)return teamsOk();let t=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),n=await e.config.onInvoke(t,e.activity);return n instanceof Response?n:n&&typeof n==`object`?Response.json(n):teamsOk()}async function dispatchInputResponses(e){let t=deriveTeamsInputResponses(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity),r=buildTeamsBinding({config:e.config,state:n}),i;try{i=await e.onInputResponse(r,e.activity)}catch(e){log.error(`Teams input response authorization failed`,{error:e});return}if(i!==null)try{await e.send({inputResponses:t},{auth:i.auth,continuationToken:resolveInputContinuationToken(e.activity,n),state:n})}catch(e){log.error(`Teams input response delivery failed`,{error:e})}}function stateFromActivity(e){let t=normalizeTeamsContinuationAddress({conversationId:e.conversation.id,replyToActivityId:teamsThreadRootActivityId(e)});return{bot:e.recipient,channelId:e.teamsChannelId??null,conversationId:e.conversation.id,conversationType:e.conversationType??e.scope,pendingAuthActivityId:null,replyToActivityId:t.replyToActivityId,serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function resolveInputContinuationToken(e,t){let n=readTeamsInputReplyToActivityId(e);return n===null?stateToken(t):teamsContinuationToken({conversationId:e.conversation.id,replyToActivityId:n,tenantId:e.tenantId})}function defaultOnInputResponse(e,t){return{auth:defaultTeamsAuth(t)}}function rejectInput(){return null}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,pendingAuthActivityId:null,replyToActivityId:null,serviceUrl:null,teamId:null,tenantId:null,triggeringUser:null}}function stateToken(e){let t=e.conversationId??``;if(!t)throw Error(`teamsChannel: missing conversation id.`);return teamsContinuationToken({conversationId:t,replyToActivityId:e.replyToActivityId,tenantId:e.tenantId})}function buildOutboundActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)return t;let n=normalizeTeamsPostInput(t),r=mergeChannelData(e,n.channelData);return{...n,channelData:r,conversation:e.conversationId?{id:e.conversationId}:void 0,from:e.bot??void 0,replyToId:e.replyToActivityId??void 0,type:`message`}}function mergeChannelData(e,t){let n={...t};return e.tenantId&&(n.tenant={id:e.tenantId}),e.teamId&&(n.team={id:e.teamId}),e.channelId&&(n.channel={id:e.channelId}),Object.keys(n).length>0?parseJsonObject(n):void 0}function teamsOk(){return new Response(`ok`,{status:200})}export{teamsChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{readNonEmptyString}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{callTeamsConnectorApi,normalizeTeamsContinuationAddress,normalizeTeamsPostInput,replyToTeamsActivity,sendTeamsActivity,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{deriveTeamsInputResponses,isTeamsInputResponseActivity,readTeamsInputReplyToActivityId,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{formatTeamsContextBlock,parseTeamsActivity,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{defaultEvents,defaultOnMessage,defaultTeamsAuth,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),o=e.onMessage??defaultOnMessage,s=e.onInputResponse??(e.onMessage===void 0?defaultOnInputResponse:rejectInput),c={...defaultEvents,...e.events};return defineChannel({kindHint:`teams`,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t),metadata:e=>({channelId:e.channelId,conversationType:e.conversationType,teamId:e.teamId}),context(t,n){return rebuildTeamsContext(t,n,e)},routes:[POST(e.route??`/eve/v1/teams`,async(n,{from:i,resolveSession:a,waitUntil:c})=>{let l=await verifyInbound(n,e.credentials);if(l===null)return new Response(`unauthorized`,{status:401});let u;try{u=parseJsonObject(JSON.parse(l))}catch(e){return log.warn(`inbound Teams body is not valid JSON`,{error:e}),teamsOk()}let d=parseTeamsActivity(u);return d===null?teamsOk():d.type===`message`?(c(isTeamsInputResponseActivity(d)?dispatchInputResponses({activity:d,config:e,from:i,onInputResponse:s}):dispatchMessage({activity:d,config:e,filesPolicy:t,onMessage:o,from:i,resolveSession:a})),teamsOk()):d.type===`invoke`?handleInvoke({activity:d,config:e,onInputResponse:s,waitUntil:c,from:i}):teamsOk()})],async receive(t,{from:r}){let i=t.target,a=readNonEmptyString(i.serviceUrl),o=readNonEmptyString(i.conversationId);if(!a||!o)throw Error(`teamsChannel().receive requires target.serviceUrl and target.conversationId.`);let s=readNonEmptyString(i.conversationType)??null,c=readNonEmptyString(i.replyToActivityId)??null,l=i.initialMessage;if(l!==void 0&&c!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let u={...initialTeamsState(),channelId:readNonEmptyString(i.channelId)??null,conversationId:o,conversationType:s,replyToActivityId:c,serviceUrl:a,teamId:readNonEmptyString(i.teamId)??null,tenantId:readNonEmptyString(i.tenantId)??null};if(l!==void 0){let t=await buildTeamsBinding({config:e,state:u}).thread.post(l);s!==`personal`&&t.id&&(c=t.id,u.replyToActivityId=t.id)}return r(teamsContinuationToken({conversationId:o,replyToActivityId:c,tenantId:u.tenantId})).send(t.message,{auth:t.auth,state:u})},events:c})}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,state:e}}function buildTeamsBinding(e){let n=buildTeamsHandle(e);return{teams:n,thread:{mentionUser:teamsMentionUser,post(e){return n.sendActivity(e)},async startTyping(){try{await n.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,t){return n.updateActivity(e,t)}}}}function buildTeamsHandle(e){let t=e.state,n=e.config.api,r=e.config.credentials;function requireAddress(){let e=t.conversationId??``,n=t.serviceUrl??``;if(!e||!n)throw Error(`teamsChannel: missing serviceUrl or conversationId for outbound message.`);return{conversationId:e,serviceUrl:n}}function anchor(n){if(!n.id||t.replyToActivityId||t.conversationType===`personal`)return;t.replyToActivityId=n.id;let r=t.conversationId;r&&e.session?.continuation?.rekey(teamsContinuationToken({conversationId:r,replyToActivityId:n.id,tenantId:t.tenantId}))}async function send(e){let i=requireAddress(),a=buildOutboundActivity(t,e),o=t.replyToActivityId===null?await sendTeamsActivity({...n,body:a,credentials:r,conversationId:i.conversationId,serviceUrl:i.serviceUrl}):await replyToTeamsActivity({...n,body:a,credentials:r,activityId:t.replyToActivityId,conversationId:i.conversationId,serviceUrl:i.serviceUrl});return anchor(o),o}return{channelId:t.channelId??void 0,conversationId:t.conversationId??``,conversationType:t.conversationType??void 0,replyToActivityId:t.replyToActivityId??void 0,serviceUrl:t.serviceUrl??``,teamId:t.teamId??void 0,tenantId:t.tenantId??void 0,request(e,t,i){let a=requireAddress();return callTeamsConnectorApi({...n,body:t,credentials:r,method:i?.method,path:e,serviceUrl:a.serviceUrl})},sendActivity:send,replyToActivity(e){let i=requireAddress(),a=t.replyToActivityId??``;if(!a)throw Error(`teamsChannel: missing reply activity id.`);return replyToTeamsActivity({...n,body:buildOutboundActivity(t,e),credentials:r,activityId:a,conversationId:i.conversationId,serviceUrl:i.serviceUrl})},updateActivity(e,i){let a=requireAddress();return updateTeamsActivity({...n,body:buildOutboundActivity(t,i),credentials:r,activityId:e,conversationId:a.conversationId,serviceUrl:a.serviceUrl})},async startTyping(){let e=requireAddress();await triggerTeamsTypingIndicator({...n,credentials:r,conversationId:e.conversationId,serviceUrl:e.serviceUrl})}}}async function verifyInbound(e,t){try{return await verifyTeamsRequest(e,{appId:t?.webhookVerifier?void 0:t?.appId,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`teams inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r=stateToken(t),i={...n,isSubscribed:async()=>await e.resolveSession(r)!==void 0},a;try{a=await e.onMessage(i,e.activity)}catch(e){log.error(`Teams message handler failed`,{error:e});return}if(a==null)return;let o=collectTeamsFileParts(e.activity.attachments,e.filesPolicy),s=buildTeamsTurnMessage(e.activity.text,o),c={activityId:e.activity.id,channelId:e.activity.teamsChannelId,conversationId:e.activity.conversation.id,conversationType:e.activity.conversationType,scope:e.activity.scope,teamId:e.activity.teamId,tenantId:e.activity.tenantId,userId:e.activity.from.id,userName:e.activity.from.name},l=a.context??[];try{await e.from(r).send(s,{auth:a.auth,context:[formatTeamsContextBlock(c),...l],state:t})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){if(isTeamsInputResponseActivity(e.activity))return e.waitUntil(dispatchInputResponses({activity:e.activity,config:e.config,onInputResponse:e.onInputResponse,from:e.from})),Response.json(teamsInvokeResponse());if(e.config.onInvoke===void 0)return teamsOk();let t=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),n=await e.config.onInvoke(t,e.activity);return n instanceof Response?n:n&&typeof n==`object`?Response.json(n):teamsOk()}async function dispatchInputResponses(e){let t=deriveTeamsInputResponses(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity),r=buildTeamsBinding({config:e.config,state:n}),i;try{i=await e.onInputResponse(r,e.activity)}catch(e){log.error(`Teams input response authorization failed`,{error:e});return}if(i!==null)try{await e.from(resolveInputContinuationToken(e.activity,n)).respond(t,{auth:i.auth})}catch(e){log.error(`Teams input response delivery failed`,{error:e})}}function stateFromActivity(e){let t=normalizeTeamsContinuationAddress({conversationId:e.conversation.id,replyToActivityId:teamsThreadRootActivityId(e)});return{bot:e.recipient,channelId:e.teamsChannelId??null,conversationId:e.conversation.id,conversationType:e.conversationType??e.scope,pendingAuthActivityId:null,replyToActivityId:t.replyToActivityId,serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function resolveInputContinuationToken(e,t){let n=readTeamsInputReplyToActivityId(e);return n===null?stateToken(t):teamsContinuationToken({conversationId:e.conversation.id,replyToActivityId:n,tenantId:e.tenantId})}function defaultOnInputResponse(e,t){return{auth:defaultTeamsAuth(t)}}function rejectInput(){return null}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,pendingAuthActivityId:null,replyToActivityId:null,serviceUrl:null,teamId:null,tenantId:null,triggeringUser:null}}function stateToken(e){let t=e.conversationId??``;if(!t)throw Error(`teamsChannel: missing conversation id.`);return teamsContinuationToken({conversationId:t,replyToActivityId:e.replyToActivityId,tenantId:e.tenantId})}function buildOutboundActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)return t;let n=normalizeTeamsPostInput(t),r=mergeChannelData(e,n.channelData);return{...n,channelData:r,conversation:e.conversationId?{id:e.conversationId}:void 0,from:e.bot??void 0,replyToId:e.replyToActivityId??void 0,type:`message`}}function mergeChannelData(e,t){let n={...t};return e.tenantId&&(n.tenant={id:e.tenantId}),e.teamId&&(n.team={id:e.teamId}),e.channelId&&(n.channel={id:e.channelId}),Object.keys(n).length>0?parseJsonObject(n):void 0}function teamsOk(){return new Response(`ok`,{status:200})}export{teamsChannel};
@@ -1,7 +1,7 @@
1
1
  import type { TelegramInstrumentationMetadata } from "#public/channels/telegram/index.js";
2
2
  import type { SessionAuthContext } from "#channel/types.js";
3
3
  import type { SessionContext } from "#public/definitions/callback-context.js";
4
- import type { ChannelSessionOps } from "#public/definitions/channel.js";
4
+ import type { ChannelContinuationOps } from "#public/definitions/channel.js";
5
5
  import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
6
6
  import { type TelegramApiOptions, type TelegramApiResponse, type TelegramCredentials, type TelegramMessageBody, type TelegramMessageResult } from "#public/channels/telegram/api.js";
7
7
  import { type TelegramHitlState } from "#public/channels/telegram/hitl.js";
@@ -23,8 +23,8 @@ export interface TelegramContext {
23
23
  export interface TelegramChannelContext extends TelegramContext {
24
24
  state: TelegramChannelState;
25
25
  }
26
- /** Event-handler Telegram context, including session operations. */
27
- export interface TelegramEventContext extends TelegramChannelContext, ChannelSessionOps {
26
+ /** Event-handler Telegram context, including continuation routing. */
27
+ export interface TelegramEventContext extends TelegramChannelContext, ChannelContinuationOps {
28
28
  }
29
29
  /** JSON-serializable Telegram channel state. */
30
30
  export interface TelegramChannelState extends TelegramHitlState {
@@ -63,10 +63,11 @@ export type TelegramInboundResult = {
63
63
  export type TelegramInboundResultOrPromise = TelegramInboundResult | Promise<TelegramInboundResult>;
64
64
  type TelegramEventHandler<T extends UnstampedMessageStreamEvent["type"]> = (data: EventData<T>, channel: TelegramEventContext, ctx: SessionContext) => void | Promise<void>;
65
65
  type TelegramSessionFailedHandler = (data: EventData<"session.failed">, channel: TelegramEventContext) => void | Promise<void>;
66
- /** Per-event handlers for `telegramChannel({ events })`. Each entry overrides the built-in default (handlers merge over {@link defaultEvents}). `session.failed` receives `(data, channel)`; all others also receive the {@link SessionContext}. */
66
+ /** Per-event handlers for `telegramChannel({ events })`. Each entry overrides the built-in default (handlers merge over {@link defaultEvents}). `session.failed` receives `(data, channel)` and exposes the ID as `data.sessionId`; all others also receive the {@link SessionContext}. */
67
67
  export interface TelegramChannelEvents {
68
68
  readonly "turn.started"?: TelegramEventHandler<"turn.started">;
69
69
  readonly "actions.requested"?: TelegramEventHandler<"actions.requested">;
70
+ readonly "action.partial"?: TelegramEventHandler<"action.partial">;
70
71
  readonly "action.result"?: TelegramEventHandler<"action.result">;
71
72
  readonly "message.completed"?: TelegramEventHandler<"message.completed">;
72
73
  readonly "message.appended"?: TelegramEventHandler<"message.appended">;
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},c=defineChannel({kindHint:`telegram`,state:initialTelegramState(e.botUsername),metadata:e=>({chatId:e.chatId,chatType:e.chatType,triggeringUserId:e.triggeringUserId??null}),fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/eve/v1/telegram`,async(r,{send:a,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let u=parseTelegramUpdate(c);return u===null?new Response(`ok`):u.kind===`message`?(o(dispatchMessage({config:e,message:u.message,onMessage:n,send:a,uploadPolicy:t})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:u.callbackQuery,send:a})),new Response(`ok`))})],async receive(t,{send:n}){let r=t.target,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires target.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readOptionalString(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c={...initialTelegramState(e.botUsername),chatId:i,conversationId:o??null,messageThreadId:a??null};return s!==void 0&&await buildTelegramHandle({config:e,state:c}).sendMessage(s),n(t.message,{auth:t.auth,continuationToken:continuationTokenFromState(c),state:c})},events:r});return attachTelegramDeliver(c),c}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let n=e.config.api,r=e.state,i=e.config.credentials;function anchor(t){let n=r.chatType??t.chatType??null;r.chatType===null&&t.chatType!==void 0&&(r.chatType=t.chatType),!(!t.id||!shouldAnchorTelegramConversation(n))&&(r.conversationId=t.id,r.chatId&&e.session?.setContinuationToken(telegramContinuationToken({chatId:r.chatId,conversationId:t.id,messageThreadId:r.messageThreadId??void 0})))}async function sendOne(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for outbound message.`);let a=await sendTelegramMessage({apiBaseUrl:n?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??r.messageThreadId??void 0},credentials:i,fetch:n?.fetch,fileBaseUrl:n?.fileBaseUrl,chatId:t});return anchor(a),a}return{botUsername:r.botUsername??e.config.botUsername,chatId:r.chatId??``,chatType:r.chatType??void 0,conversationId:r.conversationId??void 0,messageThreadId:r.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:n?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:i,fetch:n?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:n?.apiBaseUrl,chatId:t,credentials:i,fetch:n?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},post(e){return postTelegramMessage(e,sendOne)},request(e,t){return callTelegramApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:i?.botToken,fetch:n?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let a=r.chatId??``;if(a)try{await sendTelegramChatAction({action:e,apiBaseUrl:n?.apiBaseUrl,chatId:a,credentials:i,fetch:n?.fetch,messageThreadId:r.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:a})}}}}function shouldAnchorTelegramConversation(e){return e===`group`||e===`supergroup`}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromMessage(e.message,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=buildTelegramTurnMessage(e.message,i),o=formatTelegramContextBlock({botUsername:e.config.botUsername,chatId:e.message.chat.id,chatTitle:e.message.chat.title,chatType:e.message.chat.type,messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),s=r.context??[],l=e.message.text||e.message.caption,u=e.message.replyToMessage?.from?.isBot===!0&&l.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:l})]:void 0;try{await e.send({inputResponses:u,message:a,context:[o,...s]},{auth:r.auth,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromCallbackQuery(e.query,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.send({inputResponses:[telegramCallbackInputResponse(e.query.data)]},{auth:null,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function stateFromMessage(e,t){let n=e.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:e.chat.id,chatType:e.chat.type,conversationId:n?null:conversationIdForMessage(e),messageThreadId:e.messageThreadId??null,triggeringUserId:e.from?.id??null}}function stateFromCallbackQuery(e,t){let n=e.message;if(!n)return{...initialTelegramState(t.botUsername),triggeringUserId:e.from.id};let r=n.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:n.chat.id,chatType:n.chat.type,conversationId:r?null:n.messageId,messageThreadId:n.messageThreadId??null,triggeringUserId:e.from.id}}function conversationIdForMessage(e){return e.replyToMessage?.from?.isBot===!0?e.replyToMessage.messageId:e.messageId}function continuationTokenFromState(e){return telegramContinuationToken({chatId:e.chatId??``,conversationId:e.chatType===`private`?void 0:e.conversationId??void 0,messageThreadId:e.messageThreadId??void 0})}function initialTelegramState(e){return{botUsername:e??null,chatId:null,chatType:null,conversationId:null,hitlCallbacks:{},messageThreadId:null,nextHitlCallbackId:0,pendingFreeformReplies:{},triggeringUserId:null}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}function readOptionalString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},c=defineChannel({kindHint:`telegram`,state:initialTelegramState(e.botUsername),metadata:e=>({chatId:e.chatId,chatType:e.chatType,triggeringUserId:e.triggeringUserId??null}),fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/eve/v1/telegram`,async(r,{from:a,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let u=parseTelegramUpdate(c);return u===null?new Response(`ok`):u.kind===`message`?(o(dispatchMessage({config:e,message:u.message,onMessage:n,uploadPolicy:t,from:a})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:u.callbackQuery,from:a})),new Response(`ok`))})],async receive(t,{from:n}){let r=t.target,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires target.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readOptionalString(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c={...initialTelegramState(e.botUsername),chatId:i,conversationId:o??null,messageThreadId:a??null};return s!==void 0&&await buildTelegramHandle({config:e,state:c}).sendMessage(s),n(continuationTokenFromState(c)).send(t.message,{auth:t.auth,state:c})},events:r});return attachTelegramDeliver(c),c}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let n=e.config.api,r=e.state,i=e.config.credentials;function anchor(t){let n=r.chatType??t.chatType??null;r.chatType===null&&t.chatType!==void 0&&(r.chatType=t.chatType),!(!t.id||!shouldAnchorTelegramConversation(n))&&(r.conversationId=t.id,r.chatId&&e.session?.continuation?.rekey(telegramContinuationToken({chatId:r.chatId,conversationId:t.id,messageThreadId:r.messageThreadId??void 0})))}async function sendOne(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for outbound message.`);let a=await sendTelegramMessage({apiBaseUrl:n?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??r.messageThreadId??void 0},credentials:i,fetch:n?.fetch,fileBaseUrl:n?.fileBaseUrl,chatId:t});return anchor(a),a}return{botUsername:r.botUsername??e.config.botUsername,chatId:r.chatId??``,chatType:r.chatType??void 0,conversationId:r.conversationId??void 0,messageThreadId:r.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:n?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:i,fetch:n?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:n?.apiBaseUrl,chatId:t,credentials:i,fetch:n?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},post(e){return postTelegramMessage(e,sendOne)},request(e,t){return callTelegramApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:i?.botToken,fetch:n?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let a=r.chatId??``;if(a)try{await sendTelegramChatAction({action:e,apiBaseUrl:n?.apiBaseUrl,chatId:a,credentials:i,fetch:n?.fetch,messageThreadId:r.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:a})}}}}function shouldAnchorTelegramConversation(e){return e===`group`||e===`supergroup`}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromMessage(e.message,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=buildTelegramTurnMessage(e.message,i),o=formatTelegramContextBlock({botUsername:e.config.botUsername,chatId:e.message.chat.id,chatTitle:e.message.chat.title,chatType:e.message.chat.type,messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),s=r.context??[],l=e.message.text||e.message.caption,u=e.message.replyToMessage?.from?.isBot===!0&&l.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:l})]:void 0;try{let n=e.from(continuationTokenFromState(t));u===void 0?await n.send(a,{auth:r.auth,context:[o,...s],state:t}):await n.respond(u,{auth:r.auth,context:[o,...s]})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromCallbackQuery(e.query,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.from(continuationTokenFromState(t)).respond([telegramCallbackInputResponse(e.query.data)],{auth:null})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function stateFromMessage(e,t){let n=e.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:e.chat.id,chatType:e.chat.type,conversationId:n?null:conversationIdForMessage(e),messageThreadId:e.messageThreadId??null,triggeringUserId:e.from?.id??null}}function stateFromCallbackQuery(e,t){let n=e.message;if(!n)return{...initialTelegramState(t.botUsername),triggeringUserId:e.from.id};let r=n.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:n.chat.id,chatType:n.chat.type,conversationId:r?null:n.messageId,messageThreadId:n.messageThreadId??null,triggeringUserId:e.from.id}}function conversationIdForMessage(e){return e.replyToMessage?.from?.isBot===!0?e.replyToMessage.messageId:e.messageId}function continuationTokenFromState(e){return telegramContinuationToken({chatId:e.chatId??``,conversationId:e.chatType===`private`?void 0:e.conversationId??void 0,messageThreadId:e.messageThreadId??void 0})}function initialTelegramState(e){return{botUsername:e??null,chatId:null,chatType:null,conversationId:null,hitlCallbacks:{},messageThreadId:null,nextHitlCallbackId:0,pendingFreeformReplies:{},triggeringUserId:null}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}function readOptionalString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
@@ -1,6 +1,6 @@
1
1
  import type { SessionAuthContext } from "#channel/types.js";
2
2
  import type { SessionContext } from "#public/definitions/callback-context.js";
3
- import type { ChannelSessionOps } from "#public/definitions/channel.js";
3
+ import type { ChannelContinuationOps } from "#public/definitions/channel.js";
4
4
  import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
5
5
  import { type TwilioApiOptions, type TwilioApiResponse, type TwilioCredentials } from "#public/channels/twilio/api.js";
6
6
  import { type TwilioTextMessage, type TwilioVoiceCall, type TwilioVoiceTranscription } from "#public/channels/twilio/inbound.js";
@@ -19,8 +19,8 @@ export interface TwilioContext {
19
19
  export interface TwilioChannelContext extends TwilioContext {
20
20
  state: TwilioChannelState;
21
21
  }
22
- /** Event-handler Twilio context, including session operations. */
23
- export interface TwilioEventContext extends TwilioChannelContext, ChannelSessionOps {
22
+ /** Event-handler Twilio context, including continuation routing. */
23
+ export interface TwilioEventContext extends TwilioChannelContext, ChannelContinuationOps {
24
24
  }
25
25
  /** JSON-serializable state for the phone-number conversation. */
26
26
  export interface TwilioChannelState {
@@ -88,6 +88,7 @@ type TwilioSessionFailedHandler = (data: EventData<"session.failed">, channel: T
88
88
  export interface TwilioChannelEvents {
89
89
  readonly "turn.started"?: TwilioEventHandler<"turn.started">;
90
90
  readonly "actions.requested"?: TwilioEventHandler<"actions.requested">;
91
+ readonly "action.partial"?: TwilioEventHandler<"action.partial">;
91
92
  readonly "action.result"?: TwilioEventHandler<"action.result">;
92
93
  readonly "message.completed"?: TwilioEventHandler<"message.completed">;
93
94
  readonly "message.appended"?: TwilioEventHandler<"message.appended">;
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{readNonEmptyString}from"#shared/guards.js";import{GET,POST,defineChannel}from"#public/definitions/channel.js";import"#public/channels/twilio/verify.js";import{callTwilioApi,sendTwilioMessage,twilioContinuationToken,updateTwilioCall}from"#public/channels/twilio/api.js";import{emptyTwilioResponse,gatherSpeechTwilioResponse,sayTwilioResponse}from"#public/channels/twilio/twiml.js";import{defaultEvents,defaultOnText,defaultOnVoice,defaultOnVoiceTranscription}from"#public/channels/twilio/defaults.js";import{formatTwilioContextBlock,parseTwilioTextMessage,parseTwilioVoiceCall,parseTwilioVoiceTranscription}from"#public/channels/twilio/inbound.js";import{buildTwilioActionUrl,buildTwilioRoutes,verifyTwilioInbound}from"#public/channels/twilio/routing.js";const log=createLogger(`twilio.channel`);function twilioChannel(e){assertAllowFromConfigured(e);let a=buildTwilioRoutes(e.route??`/eve/v1/twilio`),o=e.onText??defaultOnText,c=e.onVoice??defaultOnVoice,l=e.onVoiceTranscription??defaultOnVoiceTranscription,u={...defaultEvents,...e.events},d=handleTwilioMessages({config:e,onText:o}),f=handleTwilioVoice({config:e,onVoice:c,routes:a}),p=handleTwilioTranscription({config:e,onVoiceTranscription:l,routes:a});return defineChannel({kindHint:`twilio`,state:{from:null,to:null,lastCallSid:null,lastMessageSid:null},metadata(e){return{from:e.from,lastCallSid:e.lastCallSid??null,lastMessageSid:e.lastMessageSid??null,to:e.to}},context(t,n){return rebuildTwilioContext(t,n,e)},routes:[GET(a.messages,d),POST(a.messages,d),GET(a.voice,f),POST(a.voice,f),GET(a.transcription,p),POST(a.transcription,p)],async receive(n,{send:r}){let i=readNonEmptyString(n.target.phoneNumber);if(!i)throw Error(`twilioChannel().receive requires target.phoneNumber.`);let a=readNonEmptyString(n.target.from)??e.messaging?.from??null;return r(n.message,{auth:n.auth,continuationToken:twilioContinuationToken(i,a??void 0),state:{from:i,lastCallSid:null,lastMessageSid:null,to:a}})},events:u})}function handleTwilioMessages(e){return async(t,{send:n,waitUntil:r})=>{let i=await verifyTwilioInbound(t,e.config);if(i===null)return new Response(`unauthorized`,{status:401});let a=parseTwilioTextMessage(i.params);return a?await isAllowed(a.from,e.config.allowFrom)?(r(dispatchText({config:e.config,message:a,onText:e.onText,send:n})),emptyTwilioResponse()):new Response(`forbidden`,{status:403}):emptyTwilioResponse()}}function handleTwilioVoice(e){return async t=>{let n=await verifyTwilioInbound(t,e.config);if(n===null)return new Response(`unauthorized`,{status:401});let r=parseTwilioVoiceCall(n.params);if(!r)return sayTwilioResponse(`Missing caller information.`);if(!await isAllowed(r.from,e.config.allowFrom))return new Response(`forbidden`,{status:403});let i=await acceptVoiceCall({call:r,config:e.config,onVoice:e.onVoice});if(i===null)return new Response(`forbidden`,{status:403});let a=i??{};return gatherSpeechTwilioResponse({actionUrl:await buildTwilioActionUrl(t,e.config,e.routes.transcription),hints:a.hints??e.config.voice?.hints,language:a.language??e.config.voice?.language,profanityFilter:a.profanityFilter??e.config.voice?.profanityFilter,prompt:a.prompt??e.config.voice?.prompt??`Please say your message after the tone.`,speechModel:a.speechModel??e.config.voice?.speechModel,speechTimeout:a.speechTimeout??e.config.voice?.speechTimeout??`auto`,timeoutSeconds:a.timeoutSeconds??e.config.voice?.timeoutSeconds,voice:a.voice??e.config.voice?.voice})}}function handleTwilioTranscription(e){return async(t,{send:n,waitUntil:r})=>{let i=await verifyTwilioInbound(t,e.config);if(i===null)return new Response(`unauthorized`,{status:401});let a=parseTwilioVoiceTranscription(i.params);return a?await isAllowed(a.from,e.config.allowFrom)?(r(dispatchVoiceTranscription({config:e.config,onVoiceTranscription:e.onVoiceTranscription,send:n,transcription:a})),sayTwilioResponse(e.config.voice?.acknowledgement??`Thanks. I'll follow up by text.`)):new Response(`forbidden`,{status:403}):gatherSpeechTwilioResponse({actionUrl:await buildTwilioActionUrl(t,e.config,e.routes.transcription),language:e.config.voice?.language,prompt:e.config.voice?.prompt??`Please say your message after the tone.`,speechTimeout:e.config.voice?.speechTimeout??`auto`,timeoutSeconds:e.config.voice?.timeoutSeconds})}}function rebuildTwilioContext(e,t,n){return{state:e,twilio:buildTwilioHandle({callSid:e.lastCallSid??void 0,config:n,from:e.from??``,to:e.to??void 0})}}function buildTwilioHandle(e){let t=e.config.api,n=e.config.credentials,r=e.config.messaging?.from??e.to,i=e.config.messaging?.messagingServiceSid,s=e.config.messaging?.statusCallbackUrl;return{callSid:e.callSid,from:e.from,to:e.to,request(e,r){return callTwilioApi({apiBaseUrl:t?.apiBaseUrl,body:r,credentials:n,fetch:t?.fetch,path:e})},sendMessage(a,c){return sendTwilioMessage({apiBaseUrl:t?.apiBaseUrl,body:a,credentials:n,fetch:t?.fetch,from:c?.from??r,messagingServiceSid:c?.messagingServiceSid??i,statusCallbackUrl:c?.statusCallbackUrl??s,to:c?.to??e.from})},updateCall(e,r){return updateTwilioCall({apiBaseUrl:t?.apiBaseUrl,callSid:e,credentials:n,fetch:t?.fetch,twiml:r})}}}function assertAllowFromConfigured(e){if(e?.allowFrom===void 0)throw Error(`twilioChannel requires allowFrom. Use allowFrom: "*" to allow all numbers.`)}async function dispatchText(e){let{message:t}=e,n={twilio:buildTwilioHandle({callSid:void 0,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onText(n,t)}catch(e){log.error(`text handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({channel:`text`,from:t.from,messageSid:t.messageSid,to:t.to});try{await e.send({message:t.body,context:[i]},{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:null,lastMessageSid:t.messageSid??null,to:t.to??null}})}catch(e){log.error(`text delivery failed`,{error:e})}}async function acceptVoiceCall(e){let{call:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})};try{return await e.onVoice(n,t)}catch(e){return log.error(`voice handler failed`,{error:e}),null}}async function dispatchVoiceTranscription(e){let{transcription:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onVoiceTranscription(n,t)}catch(e){log.error(`voice transcription handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({callSid:t.callSid,channel:`voice`,from:t.from,to:t.to});try{await e.send({message:t.text,context:[i]},{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:t.callSid??null,lastMessageSid:null,to:t.to??null}})}catch(e){log.error(`voice transcription delivery failed`,{error:e})}}async function isAllowed(e,t){let n=typeof t==`function`?await t():t;return n===`*`?!0:typeof n==`string`?n===e:n.includes(e)}export{twilioChannel};
1
+ import{createLogger}from"#internal/logging.js";import{readNonEmptyString}from"#shared/guards.js";import{GET,POST,defineChannel}from"#public/definitions/channel.js";import"#public/channels/twilio/verify.js";import{callTwilioApi,sendTwilioMessage,twilioContinuationToken,updateTwilioCall}from"#public/channels/twilio/api.js";import{emptyTwilioResponse,gatherSpeechTwilioResponse,sayTwilioResponse}from"#public/channels/twilio/twiml.js";import{defaultEvents,defaultOnText,defaultOnVoice,defaultOnVoiceTranscription}from"#public/channels/twilio/defaults.js";import{formatTwilioContextBlock,parseTwilioTextMessage,parseTwilioVoiceCall,parseTwilioVoiceTranscription}from"#public/channels/twilio/inbound.js";import{buildTwilioActionUrl,buildTwilioRoutes,verifyTwilioInbound}from"#public/channels/twilio/routing.js";const log=createLogger(`twilio.channel`);function twilioChannel(e){assertAllowFromConfigured(e);let a=buildTwilioRoutes(e.route??`/eve/v1/twilio`),o=e.onText??defaultOnText,c=e.onVoice??defaultOnVoice,l=e.onVoiceTranscription??defaultOnVoiceTranscription,u={...defaultEvents,...e.events},d=handleTwilioMessages({config:e,onText:o}),f=handleTwilioVoice({config:e,onVoice:c,routes:a}),p=handleTwilioTranscription({config:e,onVoiceTranscription:l,routes:a});return defineChannel({kindHint:`twilio`,state:{from:null,to:null,lastCallSid:null,lastMessageSid:null},metadata(e){return{from:e.from,lastCallSid:e.lastCallSid??null,lastMessageSid:e.lastMessageSid??null,to:e.to}},context(t,n){return rebuildTwilioContext(t,n,e)},routes:[GET(a.messages,d),POST(a.messages,d),GET(a.voice,f),POST(a.voice,f),GET(a.transcription,p),POST(a.transcription,p)],async receive(n,{from:r}){let i=readNonEmptyString(n.target.phoneNumber);if(!i)throw Error(`twilioChannel().receive requires target.phoneNumber.`);let a=readNonEmptyString(n.target.from)??e.messaging?.from??null;return r(twilioContinuationToken(i,a??void 0)).send(n.message,{auth:n.auth,state:{from:i,lastCallSid:null,lastMessageSid:null,to:a}})},events:u})}function handleTwilioMessages(e){return async(t,{from:n,waitUntil:r})=>{let i=await verifyTwilioInbound(t,e.config);if(i===null)return new Response(`unauthorized`,{status:401});let a=parseTwilioTextMessage(i.params);return a?await isAllowed(a.from,e.config.allowFrom)?(r(dispatchText({config:e.config,message:a,onText:e.onText,from:n})),emptyTwilioResponse()):new Response(`forbidden`,{status:403}):emptyTwilioResponse()}}function handleTwilioVoice(e){return async t=>{let n=await verifyTwilioInbound(t,e.config);if(n===null)return new Response(`unauthorized`,{status:401});let r=parseTwilioVoiceCall(n.params);if(!r)return sayTwilioResponse(`Missing caller information.`);if(!await isAllowed(r.from,e.config.allowFrom))return new Response(`forbidden`,{status:403});let i=await acceptVoiceCall({call:r,config:e.config,onVoice:e.onVoice});if(i===null)return new Response(`forbidden`,{status:403});let a=i??{};return gatherSpeechTwilioResponse({actionUrl:await buildTwilioActionUrl(t,e.config,e.routes.transcription),hints:a.hints??e.config.voice?.hints,language:a.language??e.config.voice?.language,profanityFilter:a.profanityFilter??e.config.voice?.profanityFilter,prompt:a.prompt??e.config.voice?.prompt??`Please say your message after the tone.`,speechModel:a.speechModel??e.config.voice?.speechModel,speechTimeout:a.speechTimeout??e.config.voice?.speechTimeout??`auto`,timeoutSeconds:a.timeoutSeconds??e.config.voice?.timeoutSeconds,voice:a.voice??e.config.voice?.voice})}}function handleTwilioTranscription(e){return async(t,{from:n,waitUntil:r})=>{let i=await verifyTwilioInbound(t,e.config);if(i===null)return new Response(`unauthorized`,{status:401});let a=parseTwilioVoiceTranscription(i.params);return a?await isAllowed(a.from,e.config.allowFrom)?(r(dispatchVoiceTranscription({config:e.config,onVoiceTranscription:e.onVoiceTranscription,from:n,transcription:a})),sayTwilioResponse(e.config.voice?.acknowledgement??`Thanks. I'll follow up by text.`)):new Response(`forbidden`,{status:403}):gatherSpeechTwilioResponse({actionUrl:await buildTwilioActionUrl(t,e.config,e.routes.transcription),language:e.config.voice?.language,prompt:e.config.voice?.prompt??`Please say your message after the tone.`,speechTimeout:e.config.voice?.speechTimeout??`auto`,timeoutSeconds:e.config.voice?.timeoutSeconds})}}function rebuildTwilioContext(e,t,n){return{state:e,twilio:buildTwilioHandle({callSid:e.lastCallSid??void 0,config:n,from:e.from??``,to:e.to??void 0})}}function buildTwilioHandle(e){let t=e.config.api,n=e.config.credentials,r=e.config.messaging?.from??e.to,i=e.config.messaging?.messagingServiceSid,s=e.config.messaging?.statusCallbackUrl;return{callSid:e.callSid,from:e.from,to:e.to,request(e,r){return callTwilioApi({apiBaseUrl:t?.apiBaseUrl,body:r,credentials:n,fetch:t?.fetch,path:e})},sendMessage(a,c){return sendTwilioMessage({apiBaseUrl:t?.apiBaseUrl,body:a,credentials:n,fetch:t?.fetch,from:c?.from??r,messagingServiceSid:c?.messagingServiceSid??i,statusCallbackUrl:c?.statusCallbackUrl??s,to:c?.to??e.from})},updateCall(e,r){return updateTwilioCall({apiBaseUrl:t?.apiBaseUrl,callSid:e,credentials:n,fetch:t?.fetch,twiml:r})}}}function assertAllowFromConfigured(e){if(e?.allowFrom===void 0)throw Error(`twilioChannel requires allowFrom. Use allowFrom: "*" to allow all numbers.`)}async function dispatchText(e){let{message:t}=e,n={twilio:buildTwilioHandle({callSid:void 0,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onText(n,t)}catch(e){log.error(`text handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({channel:`text`,from:t.from,messageSid:t.messageSid,to:t.to});try{await e.from(twilioContinuationToken(t.from,t.to)).send(t.body,{auth:r.auth,context:[i],state:{from:t.from,lastCallSid:null,lastMessageSid:t.messageSid??null,to:t.to??null}})}catch(e){log.error(`text delivery failed`,{error:e})}}async function acceptVoiceCall(e){let{call:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})};try{return await e.onVoice(n,t)}catch(e){return log.error(`voice handler failed`,{error:e}),null}}async function dispatchVoiceTranscription(e){let{transcription:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onVoiceTranscription(n,t)}catch(e){log.error(`voice transcription handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({callSid:t.callSid,channel:`voice`,from:t.from,to:t.to});try{await e.from(twilioContinuationToken(t.from,t.to)).send(t.text,{auth:r.auth,context:[i],state:{from:t.from,lastCallSid:t.callSid??null,lastMessageSid:null,to:t.to??null}})}catch(e){log.error(`voice transcription delivery failed`,{error:e})}}async function isAllowed(e,t){let n=typeof t==`function`?await t():t;return n===`*`?!0:typeof n==`string`?n===e:n.includes(e)}export{twilioChannel};
@@ -12,8 +12,7 @@ export type { SessionAuth, SessionParent, SessionTurn };
12
12
  */
13
13
  export interface SessionContext {
14
14
  /**
15
- * Active session metadata. Mirrors the `Session` type but exposes the
16
- * identifier as `id` here, where `Session` names it `sessionId`.
15
+ * Active session metadata for the callback's exact durable session.
17
16
  */
18
17
  readonly session: {
19
18
  readonly id: string;