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.
- package/CHANGELOG.md +36 -0
- package/README.md +5 -6
- package/dist/src/acp/adapter.d.ts +10 -4
- package/dist/src/acp/adapter.js +1 -1
- package/dist/src/channel/adapter-context.d.ts +1 -1
- package/dist/src/channel/adapter.d.ts +2 -4
- package/dist/src/channel/adapter.js +1 -1
- package/dist/src/channel/channel-address.d.ts +58 -0
- package/dist/src/channel/channel-address.js +1 -0
- package/dist/src/channel/channel-operations.d.ts +72 -0
- package/dist/src/channel/channel-operations.js +1 -0
- package/dist/src/channel/compiled-channel.d.ts +10 -6
- package/dist/src/channel/cross-channel-receive.d.ts +13 -25
- package/dist/src/channel/cross-channel-receive.js +2 -2
- package/dist/src/channel/receive-target.d.ts +1 -2
- package/dist/src/channel/routes.d.ts +21 -149
- package/dist/src/channel/schedule.d.ts +1 -1
- package/dist/src/channel/schedule.js +1 -1
- package/dist/src/channel/send-input.d.ts +6 -0
- package/dist/src/channel/send-input.js +1 -0
- package/dist/src/channel/session.d.ts +44 -30
- package/dist/src/channel/session.js +1 -1
- package/dist/src/channel/types.d.ts +8 -35
- package/dist/src/chunks/{use-eve-agent-k-XHfrJT.js → use-eve-agent-CF5D6jSV.js} +361 -367
- package/dist/src/chunks/{use-eve-agent-BXMIf5fR.js → use-eve-agent-DbYZLG0h.js} +364 -368
- package/dist/src/cli/commands/agent-prompt/build-and-verify.md +1 -2
- package/dist/src/cli/commands/channel-add-conflicts.js +1 -1
- package/dist/src/cli/commands/info.d.ts +1 -1
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/commands/registry-metadata.d.ts +15 -0
- package/dist/src/cli/commands/registry-metadata.js +1 -0
- package/dist/src/cli/commands/registry-package.d.ts +46 -0
- package/dist/src/cli/commands/registry-package.js +1 -0
- package/dist/src/cli/commands/registry.d.ts +1 -1
- package/dist/src/cli/commands/registry.js +4 -4
- package/dist/src/cli/dev/tui/runner.d.ts +1 -1
- package/dist/src/cli/dev/tui/runner.js +1 -1
- package/dist/src/cli/dev/tui/subagent-pump.js +1 -1
- package/dist/src/cli/dev/tui/terminal-renderer.js +2 -2
- package/dist/src/cli/dev/tui/tui.js +1 -1
- package/dist/src/cli/invoke/invoke.d.ts +1 -1
- package/dist/src/cli/invoke/invoke.js +1 -1
- package/dist/src/cli/invoke/result.d.ts +0 -11
- package/dist/src/cli/invoke/result.js +1 -1
- package/dist/src/client/client-error.d.ts +2 -0
- package/dist/src/client/client-error.js +1 -1
- package/dist/src/client/client.d.ts +7 -14
- package/dist/src/client/client.js +1 -1
- package/dist/src/client/eve-agent-store.d.ts +4 -4
- package/dist/src/client/eve-agent-store.js +1 -1
- package/dist/src/client/index.d.ts +3 -2
- package/dist/src/client/index.js +1 -1
- package/dist/src/client/message-reducer-types.d.ts +3 -0
- package/dist/src/client/message-reducer.js +1 -1
- package/dist/src/client/message-response.d.ts +2 -7
- package/dist/src/client/message-response.js +1 -1
- package/dist/src/client/open-stream.js +1 -1
- package/dist/src/client/session-controls.d.ts +29 -0
- package/dist/src/client/session-controls.js +1 -0
- package/dist/src/client/session-utils.d.ts +0 -20
- package/dist/src/client/session-utils.js +1 -1
- package/dist/src/client/session.d.ts +26 -90
- package/dist/src/client/session.js +1 -1
- package/dist/src/client/sessions.d.ts +20 -0
- package/dist/src/client/sessions.js +1 -0
- package/dist/src/client/types.d.ts +26 -47
- package/dist/src/compiler/extension-compatibility.d.ts +10 -10
- package/dist/src/compiler/extension-compatibility.js +1 -1
- package/dist/src/compiler/normalize-subagent.js +1 -1
- package/dist/src/evals/cli/eval-client.js +1 -1
- package/dist/src/evals/context.js +1 -1
- package/dist/src/evals/session.d.ts +7 -6
- package/dist/src/evals/session.js +1 -1
- package/dist/src/evals/types.d.ts +9 -9
- package/dist/src/execution/agent-handle-dispatch.js +1 -1
- package/dist/src/execution/cancel-descendant-turns-step.js +1 -1
- package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
- package/dist/src/execution/remote-agent-dispatch.d.ts +1 -3
- package/dist/src/execution/remote-agent-dispatch.js +1 -1
- package/dist/src/execution/runtime-context.js +1 -1
- package/dist/src/execution/runtime-errors.d.ts +1 -1
- package/dist/src/execution/session-command-token.d.ts +2 -0
- package/dist/src/execution/session-command-token.js +1 -1
- package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
- package/dist/src/execution/subagent-hitl-proxy.js +1 -1
- package/dist/src/execution/tool-auth.d.ts +1 -1
- package/dist/src/execution/tool-auth.js +1 -1
- package/dist/src/execution/turn-control-receiver.d.ts +2 -2
- package/dist/src/execution/turn-workflow.js +1 -1
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/harness/cancelled-turn-emission.d.ts +1 -1
- package/dist/src/harness/cancelled-turn-emission.js +1 -1
- package/dist/src/harness/emission-state.d.ts +25 -0
- package/dist/src/harness/emission-state.js +1 -0
- package/dist/src/harness/emission.d.ts +5 -37
- package/dist/src/harness/emission.js +1 -1
- package/dist/src/harness/handles/prompt.d.ts +22 -3
- package/dist/src/harness/handles/prompt.js +1 -1
- package/dist/src/harness/handles/store.d.ts +0 -5
- package/dist/src/harness/handles/store.js +1 -1
- package/dist/src/harness/ordered-stream-emitter.d.ts +3 -2
- package/dist/src/harness/ordered-stream-emitter.js +1 -1
- package/dist/src/harness/runtime-actions.js +1 -1
- package/dist/src/harness/session-limit-enforcement.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/harness/tools.d.ts +1 -1
- package/dist/src/harness/tools.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/nitro/dev-runtime-artifacts.js +1 -1
- package/dist/src/internal/nitro/dev-runtime-source-snapshot-copy.js +1 -1
- package/dist/src/internal/nitro/dev-runtime-source-snapshot-local-roots.d.ts +15 -0
- package/dist/src/internal/nitro/dev-runtime-source-snapshot-local-roots.js +1 -0
- package/dist/src/internal/nitro/dev-runtime-source-snapshot.d.ts +14 -2
- package/dist/src/internal/nitro/dev-runtime-source-snapshot.js +1 -1
- package/dist/src/internal/nitro/host/dev-server-http.js +1 -1
- package/dist/src/internal/nitro/routes/channel-dispatch.d.ts +2 -5
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/internal/nitro/routes/channel-route-context.d.ts +4 -3
- package/dist/src/internal/nitro/routes/channel-route-context.js +1 -1
- package/dist/src/packages/eve-catalog/src/index.js +1 -1
- package/dist/src/protocol/cancel-turn.d.ts +9 -4
- package/dist/src/protocol/cancel-turn.js +1 -1
- package/dist/src/protocol/message.d.ts +27 -17
- package/dist/src/protocol/message.js +2 -2
- package/dist/src/protocol/reset-session.d.ts +1 -1
- package/dist/src/protocol/routes.d.ts +27 -47
- package/dist/src/protocol/routes.js +1 -1
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +19 -21
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
- package/dist/src/public/channels/discord/discordChannel.d.ts +5 -4
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/eve.d.ts +11 -10
- package/dist/src/public/channels/eve.js +1 -1
- package/dist/src/public/channels/github/dispatch.d.ts +8 -8
- package/dist/src/public/channels/github/dispatch.js +1 -1
- package/dist/src/public/channels/github/githubChannel.d.ts +4 -3
- package/dist/src/public/channels/github/githubChannel.js +1 -1
- package/dist/src/public/channels/github/pr-context.d.ts +1 -1
- package/dist/src/public/channels/index.d.ts +1 -1
- package/dist/src/public/channels/linear/linearChannel.d.ts +4 -3
- package/dist/src/public/channels/linear/linearChannel.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +1 -1
- package/dist/src/public/channels/slack/interactions.d.ts +3 -4
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/session-operations.d.ts +37 -0
- package/dist/src/public/channels/slack/session-operations.js +1 -0
- package/dist/src/public/channels/slack/slackChannel.d.ts +56 -74
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/teams/teamsChannel.d.ts +4 -3
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/telegram/telegramChannel.d.ts +5 -4
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/channels/twilio/twilioChannel.d.ts +4 -3
- package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
- package/dist/src/public/definitions/callback-context.d.ts +1 -2
- package/dist/src/public/definitions/channel.d.ts +22 -86
- package/dist/src/public/definitions/channel.js +1 -1
- package/dist/src/public/definitions/hook.d.ts +1 -0
- package/dist/src/public/definitions/remote-agent.js +1 -1
- package/dist/src/public/definitions/schedule.d.ts +16 -16
- package/dist/src/public/definitions/tool.d.ts +5 -5
- package/dist/src/public/schedules/index.d.ts +1 -1
- package/dist/src/react/use-eve-agent.d.ts +7 -4
- package/dist/src/react/use-eve-agent.js +1 -1
- package/dist/src/runtime/agent/bootstrap-model-utils.d.ts +11 -0
- package/dist/src/runtime/agent/bootstrap-model-utils.js +1 -1
- package/dist/src/runtime/agent/mock-model-adapter.js +1 -1
- package/dist/src/runtime/agent/mock-model-fixtures.js +2 -2
- package/dist/src/runtime/prompt/compose.js +1 -1
- package/dist/src/runtime/subagents/dynamic-remote-agent-config.js +1 -1
- package/dist/src/runtime/types.d.ts +2 -2
- package/dist/src/services/inspect-application.d.ts +1 -1
- package/dist/src/services/inspect-application.js +1 -1
- package/dist/src/setup/channel-add-conflicts.js +1 -1
- package/dist/src/setup/integrations/linear/setup.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
- package/dist/src/setup/scaffold/create/web-template.js +29 -17
- package/dist/src/shared/async-iterable.d.ts +2 -0
- package/dist/src/shared/async-iterable.js +1 -0
- package/dist/src/shared/channel-definition.d.ts +7 -7
- package/dist/src/shared/schedule-definition.d.ts +2 -2
- package/dist/src/shared/tool-definition.d.ts +1 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.d.ts +8 -5
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.d.ts +8 -5
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/README.md +6 -6
- package/docs/channels/chat-sdk.mdx +5 -6
- package/docs/channels/custom.mdx +116 -144
- package/docs/channels/discord.mdx +1 -1
- package/docs/channels/eve.mdx +76 -30
- package/docs/channels/github.mdx +1 -1
- package/docs/channels/linear.mdx +10 -10
- package/docs/channels/overview.mdx +2 -2
- package/docs/channels/slack.mdx +46 -15
- package/docs/channels/telegram.mdx +1 -1
- package/docs/channels/twilio.mdx +1 -1
- package/docs/concepts/default-harness.md +11 -3
- package/docs/concepts/execution-model-and-durability.mdx +17 -5
- package/docs/concepts/sessions-runs-and-streaming.md +45 -16
- package/docs/evals/cases.mdx +3 -3
- package/docs/evals/targets.mdx +1 -1
- package/docs/guides/auth-and-route-protection.md +2 -1
- package/docs/guides/client/continuations.mdx +50 -77
- package/docs/guides/client/messages.mdx +29 -33
- package/docs/guides/client/output-schema.mdx +7 -14
- package/docs/guides/client/overview.mdx +40 -11
- package/docs/guides/client/streaming.mdx +18 -23
- package/docs/guides/frontend/nuxt.mdx +1 -1
- package/docs/guides/frontend/overview.mdx +40 -36
- package/docs/guides/frontend/sveltekit.mdx +1 -1
- package/docs/guides/frontend/use-eve-agent-svelte.mdx +19 -23
- package/docs/guides/frontend/use-eve-agent-vue.mdx +21 -25
- package/docs/guides/hooks.md +1 -1
- package/docs/guides/instrumentation.md +1 -1
- package/docs/install-integrations.mdx +4 -2
- package/docs/patterns/dynamic-scheduling.md +16 -15
- package/docs/reference/cli.md +4 -1
- package/docs/schedules.mdx +10 -11
- package/docs/subagents.mdx +1 -1
- package/docs/tools/human-in-the-loop.md +1 -1
- package/docs/tools/overview.mdx +26 -2
- package/docs/tutorial/ship-it.mdx +1 -1
- package/package.json +1 -1
- package/dist/src/channel/cancel.d.ts +0 -6
- package/dist/src/channel/cancel.js +0 -1
- package/dist/src/channel/clear-session.d.ts +0 -4
- package/dist/src/channel/clear-session.js +0 -1
- package/dist/src/channel/compact-session.d.ts +0 -4
- package/dist/src/channel/compact-session.js +0 -1
- package/dist/src/channel/reset-session.d.ts +0 -6
- package/dist/src/channel/reset-session.js +0 -1
- package/dist/src/channel/resolve-active-session.d.ts +0 -4
- package/dist/src/channel/resolve-active-session.js +0 -1
- package/dist/src/channel/send.d.ts +0 -6
- package/dist/src/channel/send.js +0 -1
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import type {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
*
|
|
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,
|
|
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 `
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
|
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.
|
|
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 {
|
|
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
|
|
33
|
-
export interface TeamsEventContext extends TeamsChannelContext,
|
|
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,{
|
|
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 {
|
|
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
|
|
27
|
-
export interface TelegramEventContext extends TelegramChannelContext,
|
|
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,{
|
|
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 {
|
|
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
|
|
23
|
-
export interface TwilioEventContext extends TwilioChannelContext,
|
|
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,{
|
|
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
|
|
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;
|