eve 0.84.0 → 0.85.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 +46 -0
- package/dist/src/channel/channel-operations.d.ts +2 -18
- package/dist/src/channel/compiled-channel.d.ts +3 -7
- package/dist/src/channel/cross-channel-receive.d.ts +13 -11
- package/dist/src/channel/cross-channel-receive.js +1 -1
- package/dist/src/channel/delivery.js +1 -1
- package/dist/src/channel/schedule.js +1 -1
- package/dist/src/channel/session.d.ts +26 -4
- package/dist/src/channel/session.js +1 -1
- package/dist/src/channel/types.d.ts +2 -0
- package/dist/src/chunks/{use-eve-agent-BbbguIdB.js → use-eve-agent-B2jCfcnG.js} +29 -12
- package/dist/src/chunks/{use-eve-agent-DWjyzplb.js → use-eve-agent-DCLFi26P.js} +29 -12
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/client/client.js +1 -1
- package/dist/src/client/delivery.d.ts +2 -0
- package/dist/src/client/delivery.js +1 -1
- package/dist/src/client/open-stream.js +1 -1
- package/dist/src/client/optimistic-message-submissions.js +1 -1
- package/dist/src/client/session-store.js +1 -1
- package/dist/src/client/session.js +1 -1
- package/dist/src/client/sessions.d.ts +1 -0
- package/dist/src/client/sessions.js +1 -1
- package/dist/src/client/types.d.ts +7 -4
- package/dist/src/compiled/.vendor-stamp.json +2 -2
- package/dist/src/compiled/@chat-adapter/slack/index.js +13 -13
- package/dist/src/compiled/@vercel/sandbox/api-client/api-client.d.ts +75 -0
- package/dist/src/compiled/@vercel/sandbox/api-client/validators.d.ts +4 -0
- package/dist/src/compiled/@vercel/sandbox/command.d.ts +78 -0
- package/dist/src/compiled/@vercel/sandbox/drive.d.ts +41 -1
- package/dist/src/compiled/@vercel/sandbox/index.js +13 -13
- package/dist/src/compiled/@vercel/sandbox/network-policy.d.ts +35 -4
- package/dist/src/compiled/@vercel/sandbox/sandbox.d.ts +18 -4
- package/dist/src/compiled/@vercel/sandbox/session.d.ts +29 -3
- package/dist/src/compiled/@vercel/sandbox/utils/pipe-stdin.d.ts +19 -0
- package/dist/src/compiled/_chunks/client/{core-CspsxXR-.js → core-BsvaaMUJ.js} +1 -1
- package/dist/src/compiled/_chunks/node/{auth-Dztlx9pE.js → auth-v5pS3mLo.js} +1 -1
- package/dist/src/compiled/_chunks/node/is-stream-DsuQX_bd.js +1 -0
- package/dist/src/compiled/_chunks/node/token-DHGH-jVc.js +1 -0
- package/dist/src/compiled/_chunks/node/token-util-BH2pw9Qm.js +25 -0
- package/dist/src/compiled/_chunks/node/token-util-CcvxNv76.js +1 -0
- package/dist/src/compiled/_chunks/node/version-C2PfNyEv.js +1 -0
- package/dist/src/compiled/zod/index.js +1 -1
- package/dist/src/compiled/zod/v4/core.js +1 -1
- package/dist/src/compiled/zod-validation-error/index.js +1 -1
- package/dist/src/compiler/artifacts.d.ts +1 -0
- package/dist/src/compiler/artifacts.js +1 -1
- package/dist/src/compiler/manifest.d.ts +2 -2
- package/dist/src/compiler/manifest.js +1 -1
- package/dist/src/compiler/model-catalog.d.ts +8 -18
- package/dist/src/compiler/model-catalog.js +1 -1
- package/dist/src/compiler/normalize-agent-config.js +1 -1
- package/dist/src/compiler/normalize-manifest-types.d.ts +3 -0
- package/dist/src/compiler/normalize-manifest.js +1 -1
- package/dist/src/context/keys.d.ts +7 -8
- package/dist/src/context/keys.js +1 -1
- package/dist/src/eve-channel/index.js +1 -1
- package/dist/src/eve-channel/request.d.ts +1 -0
- package/dist/src/eve-channel/request.js +1 -1
- package/dist/src/eve-channel/support.js +1 -1
- package/dist/src/execution/agent-sessions/context.d.ts +5 -1
- package/dist/src/execution/agent-sessions/context.js +1 -1
- package/dist/src/execution/agent-sessions/steps.js +1 -1
- package/dist/src/execution/dynamic-workflow/tool.js +1 -1
- package/dist/src/execution/legacy-session/snapshot.d.ts +1 -2
- package/dist/src/execution/legacy-session/snapshot.js +1 -1
- package/dist/src/execution/proxied-deliver-step.js +1 -1
- package/dist/src/execution/public-destination.d.ts +3 -0
- package/dist/src/execution/public-destination.js +1 -1
- package/dist/src/execution/publish-session-events.js +1 -1
- package/dist/src/execution/sandbox/bindings/docker-cli.d.ts +2 -0
- package/dist/src/execution/sandbox/bindings/docker-cli.js +1 -1
- package/dist/src/execution/sandbox/bindings/docker-connection.js +1 -1
- package/dist/src/execution/sandbox/bindings/just-bash-runtime.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
- package/dist/src/execution/sandbox/multiplexed-command.d.ts +11 -1
- package/dist/src/execution/sandbox/multiplexed-command.js +1 -1
- package/dist/src/execution/sandbox/session.js +1 -1
- package/dist/src/execution/session/checkpoint-migrations.d.ts +16 -5
- package/dist/src/execution/session/checkpoint-migrations.js +1 -1
- package/dist/src/execution/session/checkpoint-refusal.d.ts +4 -0
- package/dist/src/execution/session/checkpoint-refusal.js +1 -0
- package/dist/src/execution/session/entry.js +1 -1
- package/dist/src/execution/session/handoff-steps.d.ts +7 -1
- package/dist/src/execution/session/handoff-steps.js +1 -1
- package/dist/src/execution/session/imported-conversation.d.ts +2 -0
- package/dist/src/execution/session/imported-conversation.js +1 -0
- package/dist/src/execution/session/interrupted-step.js +1 -1
- package/dist/src/execution/session/pre-v27-checkpoint.d.ts +36 -0
- package/dist/src/execution/session/pre-v27-checkpoint.js +1 -0
- package/dist/src/execution/session/program.d.ts +2 -0
- package/dist/src/execution/session/program.js +1 -1
- package/dist/src/execution/session/turn-step.js +1 -1
- package/dist/src/execution/session-callback-request.js +1 -1
- package/dist/src/execution/session-inbox/address.d.ts +1 -0
- package/dist/src/execution/session-inbox/handle.d.ts +7 -0
- package/dist/src/execution/session-inbox/handle.js +1 -0
- package/dist/src/execution/session-inbox/owner.d.ts +4 -4
- package/dist/src/execution/session-inbox/owner.js +1 -1
- package/dist/src/execution/session-inbox/resume.d.ts +1 -0
- package/dist/src/execution/session-inbox/resume.js +1 -1
- package/dist/src/execution/tool-auth.js +1 -1
- package/dist/src/execution/tools/agent-router-workflow.js +1 -1
- package/dist/src/execution/tools/channel-to.d.ts +12 -0
- package/dist/src/execution/tools/channel-to.js +1 -0
- package/dist/src/execution/tools/workflow/body.d.ts +3 -0
- package/dist/src/execution/tools/workflow/body.js +1 -1
- package/dist/src/execution/tools/workflow/step-context.d.ts +5 -0
- package/dist/src/execution/tools/workflow/step-execution.js +1 -1
- package/dist/src/execution/tools/workflow/step.js +1 -1
- package/dist/src/execution/workflow-callback-url.d.ts +2 -3
- package/dist/src/execution/workflow-callback-url.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/harness/compaction/step.js +1 -1
- package/dist/src/harness/model-call/call.d.ts +3 -1
- package/dist/src/harness/model-call/call.js +1 -1
- package/dist/src/harness/model-call/run.js +1 -1
- package/dist/src/harness/semantic-errors/rules/gateway.js +1 -1
- package/dist/src/harness/session-machine/transitions.js +1 -1
- package/dist/src/internal/application/compiled-artifacts.d.ts +2 -0
- package/dist/src/internal/application/compiled-artifacts.js +2 -2
- package/dist/src/internal/application/optional-package-install.js +1 -1
- package/dist/src/internal/application/stamped-package-version.js +1 -1
- package/dist/src/internal/attachments/limited-read.d.ts +2 -0
- package/dist/src/internal/attachments/limited-read.js +1 -1
- package/dist/src/internal/attachments/url-refs.js +1 -1
- package/dist/src/internal/capability-error.d.ts +3 -0
- package/dist/src/internal/capability-error.js +1 -0
- package/dist/src/internal/model-catalog.d.ts +39 -16
- package/dist/src/internal/model-catalog.js +1 -1
- package/dist/src/internal/nitro/host/dev-build-host.js +1 -1
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/protocol/delivery.d.ts +1 -0
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +1 -1
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/github/githubChannel.js +1 -1
- package/dist/src/public/channels/linear/linearChannel.js +1 -1
- package/dist/src/public/channels/linq/inboundContent.js +1 -1
- package/dist/src/public/channels/slack/answers.d.ts +2 -3
- package/dist/src/public/channels/slack/answers.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +23 -3
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/app-manifest.d.ts +2 -0
- package/dist/src/public/channels/slack/app-manifest.js +1 -1
- package/dist/src/public/channels/slack/auth.d.ts +2 -1
- package/dist/src/public/channels/slack/auth.js +1 -1
- package/dist/src/public/channels/slack/code-channel.d.ts +42 -0
- package/dist/src/public/channels/slack/code-channel.js +1 -0
- package/dist/src/public/channels/slack/defaults.d.ts +7 -3
- package/dist/src/public/channels/slack/defaults.js +3 -3
- package/dist/src/public/channels/slack/hitl.d.ts +7 -17
- package/dist/src/public/channels/slack/hitl.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +4 -3
- package/dist/src/public/channels/slack/index.js +1 -1
- package/dist/src/public/channels/slack/interaction-cards.d.ts +0 -2
- package/dist/src/public/channels/slack/interaction-cards.js +1 -1
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/open-code-channel.d.ts +30 -0
- package/dist/src/public/channels/slack/open-code-channel.js +1 -0
- package/dist/src/public/channels/slack/private-approval-delivery.d.ts +16 -4
- package/dist/src/public/channels/slack/private-approval-delivery.js +1 -1
- package/dist/src/public/channels/slack/renderers.d.ts +58 -6
- package/dist/src/public/channels/slack/renderers.js +1 -1
- package/dist/src/public/channels/slack/reply-delivery.d.ts +4 -0
- package/dist/src/public/channels/slack/reply-delivery.js +1 -1
- package/dist/src/public/channels/slack/request-card-render.d.ts +45 -0
- package/dist/src/public/channels/slack/request-card-render.js +1 -0
- package/dist/src/public/channels/slack/request-cards.d.ts +35 -0
- package/dist/src/public/channels/slack/request-cards.js +3 -0
- package/dist/src/public/channels/slack/sign-in-cancel.d.ts +3 -5
- package/dist/src/public/channels/slack/sign-in-cancel.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +109 -22
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/slack/task-card.d.ts +3 -3
- package/dist/src/public/channels/slack/task-card.js +1 -1
- package/dist/src/public/channels/slack/thread-status.js +1 -1
- package/dist/src/public/channels/teams/api.js +1 -1
- package/dist/src/public/channels/teams/defaults.js +1 -1
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/teams/verify.d.ts +9 -5
- package/dist/src/public/channels/teams/verify.js +1 -1
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
- package/dist/src/public/nuxt/module.js +1 -1
- package/dist/src/public/schedules/subscription.d.ts +10 -1
- package/dist/src/reactions/kinds/model.js +1 -1
- package/dist/src/reactions/kinds/subagent.js +1 -1
- package/dist/src/runtime/agent/resolve-model.d.ts +3 -4
- package/dist/src/runtime/agent/resolve-model.js +1 -1
- package/dist/src/runtime/loaders/artifact-paths.d.ts +1 -0
- package/dist/src/runtime/loaders/artifact-paths.js +1 -1
- package/dist/src/runtime/loaders/bundled-artifacts.d.ts +2 -0
- package/dist/src/runtime/loaders/bundled-artifacts.js +1 -1
- package/dist/src/runtime/loaders/model-metadata.d.ts +4 -0
- package/dist/src/runtime/loaders/model-metadata.js +1 -0
- package/dist/src/runtime/resolve-agent.js +1 -1
- package/dist/src/runtime/sessions/compiled-agent-cache.d.ts +3 -0
- package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
- package/dist/src/runtime/subagents/dynamic-agent-config.d.ts +2 -2
- package/dist/src/runtime/subagents/dynamic-agent-config.js +1 -1
- package/dist/src/runtime/subagents/workflow.js +1 -1
- package/dist/src/sandbox/provider.d.ts +2 -0
- package/dist/src/setup/flows/model-source-change.js +1 -1
- package/dist/src/setup/index.d.ts +1 -1
- package/dist/src/setup/primitives/index.d.ts +1 -1
- package/dist/src/setup/primitives/pm/run.d.ts +0 -2
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/shared/agent-definition.d.ts +0 -1
- package/dist/src/shared/channel-definition.d.ts +4 -4
- package/dist/src/shared/sandbox-session.d.ts +4 -0
- package/dist/src/subagents/adapter.js +1 -1
- package/dist/src/subagents/parent-notification.js +1 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/tools/definition.d.ts +21 -0
- package/dist/src/tools/provided/ask-question-workflow.js +1 -1
- package/dist/src/tools/provided/sleep-workflow.js +1 -1
- package/dist/src/tools/workflow-definition.d.ts +4 -4
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/agent-config.md +5 -4
- package/docs/channels/custom.mdx +16 -3
- package/docs/channels/eve.mdx +2 -0
- package/docs/channels/linq.mdx +1 -1
- package/docs/channels/slack.mdx +185 -18
- package/docs/channels/teams.mdx +6 -2
- package/docs/concepts/default-harness.md +6 -0
- package/docs/concepts/upgrade-to-v27-events.md +2 -2
- package/docs/evals/judge.mdx +13 -0
- package/docs/experimental/dynamic-schedules.mdx +2 -2
- package/docs/guides/client/overview.mdx +24 -2
- package/docs/guides/remote-agents.md +8 -0
- package/docs/patterns/fork-conversations-into-new-threads.md +159 -0
- package/docs/patterns/meta.json +1 -0
- package/docs/reference/cli.md +1 -1
- package/docs/sandbox/custom-providers.mdx +1 -1
- package/docs/sandbox/index.mdx +1 -1
- package/docs/schedules.mdx +1 -1
- package/docs/tools/overview.mdx +34 -0
- package/docs/tools/workflows.mdx +51 -5
- package/package.json +2 -2
- package/dist/src/compiled/_chunks/node/token-Bedbhv5z.js +0 -1
- package/dist/src/compiled/_chunks/node/token-util-DpJNusVS.js +0 -1
- package/dist/src/compiled/_chunks/node/token-util-DwHQ2a3c.js +0 -1
- package/dist/src/compiled/_chunks/node/version-C__MqbLM.js +0 -1
- package/dist/src/public/channels/slack/approval-cards.d.ts +0 -25
- package/dist/src/public/channels/slack/approval-cards.js +0 -2
- package/dist/src/runtime/agent/model-catalog.d.ts +0 -10
- package/dist/src/runtime/agent/model-catalog.js +0 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { ChannelEventContext, ChannelEventOf, CommittedLine } from "#public/definitions/channel.js";
|
|
2
2
|
import type { SessionEvent } from "#protocol/session-event.js";
|
|
3
3
|
import type { MessageContent } from "#shared/message-content.js";
|
|
4
|
+
import { type SessionSendOptions } from "#channel/session.js";
|
|
4
5
|
import type { ChannelResolveSession, ChannelSource } from "#channel/channel-operations.js";
|
|
5
6
|
import type { Delivery } from "#shared/delivery.js";
|
|
6
7
|
import type { SessionAuthContext, TurnPolicy } from "#channel/types.js";
|
|
7
8
|
import type { VercelConnectMetadata } from "#shared/vercel-connect-metadata.js";
|
|
8
9
|
import type { CardElement } from "#public/channels/slack/cards.js";
|
|
10
|
+
import type { BlockKitBlock } from "#public/channels/slack/blocks.js";
|
|
11
|
+
import type { SlackHitlRoute } from "#public/channels/slack/hitl.js";
|
|
9
12
|
import type { SessionContext } from "#public/definitions/callback-context.js";
|
|
10
13
|
import type { SessionHistoryMessage } from "#shared/session-history.js";
|
|
11
14
|
import type { ChannelContinuationOps } from "#public/definitions/channel.js";
|
|
@@ -83,28 +86,57 @@ export interface SlackAuthorizationEventContext {
|
|
|
83
86
|
*/
|
|
84
87
|
readonly state: SlackChannelState;
|
|
85
88
|
}
|
|
89
|
+
/** One request's controls in a posted card message. */
|
|
90
|
+
export interface SlackInteractionCardRequest {
|
|
91
|
+
readonly interactionId: string;
|
|
92
|
+
/** The controls blocks the message shows for this request now. */
|
|
93
|
+
readonly blocks: readonly BlockKitBlock[];
|
|
94
|
+
/** The separate details message, deleted once every request it covers settled. */
|
|
95
|
+
readonly detailsTs?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Set once eve sent the request's settled rendering, even if Slack refused it, so a failed
|
|
98
|
+
* update is never retried forever. The message is forgotten once every request has it.
|
|
99
|
+
*/
|
|
100
|
+
readonly settled?: true;
|
|
101
|
+
}
|
|
102
|
+
/** A message eve posted holding the controls of one or more input requests. */
|
|
103
|
+
export interface SlackInteractionCardRecord {
|
|
104
|
+
/** The message's conversation: the session's channel, or the reviewer's DM. */
|
|
105
|
+
readonly channelId: string;
|
|
106
|
+
readonly ts: string;
|
|
107
|
+
readonly destination: SlackApprovalChannel;
|
|
108
|
+
/** The session a card's controls answer back to: a direct message's thread, or a code channel. */
|
|
109
|
+
readonly route?: SlackHitlRoute;
|
|
110
|
+
/** Thread post announcing a direct-message request; updated with the outcome. */
|
|
111
|
+
readonly announcementTs?: string;
|
|
112
|
+
readonly requests: readonly SlackInteractionCardRequest[];
|
|
113
|
+
}
|
|
86
114
|
/**
|
|
87
115
|
* JSON-serializable per-session state, stored verbatim across workflow
|
|
88
116
|
* step boundaries. Anything written here must round-trip through
|
|
89
117
|
* `JSON.stringify` / `JSON.parse`.
|
|
90
118
|
*/
|
|
91
|
-
export interface SlackPendingApprovalCard {
|
|
92
|
-
/** Thread post announcing a direct-message approval; updated with the outcome. */
|
|
93
|
-
readonly announcementTs?: string;
|
|
94
|
-
/** Separate tool-input details post; deleted once none of its approvals are pending. */
|
|
95
|
-
readonly detailsMessageTs?: string;
|
|
96
|
-
/** Channel containing the approval card; differs from the session channel for DM delivery. */
|
|
97
|
-
readonly messageChannelId?: string;
|
|
98
|
-
readonly messageBlocks: readonly unknown[];
|
|
99
|
-
readonly messageTs: string;
|
|
100
|
-
}
|
|
101
119
|
export interface SlackChannelState extends UndeliveredReplyState {
|
|
102
120
|
/** Audience classification captured before the session is dispatched. */
|
|
103
121
|
audience?: ChannelAudience;
|
|
104
122
|
/** Slack channel id seeded by the inbound mention. */
|
|
105
123
|
channelId: string | null;
|
|
106
|
-
/** Slack thread root ts. */
|
|
124
|
+
/** Slack thread root ts; `null` in a code channel, which is one session across its threads. */
|
|
107
125
|
threadTs: string | null;
|
|
126
|
+
/** Whether this session serves a Slack code channel. */
|
|
127
|
+
codeChannel?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* In a code channel, the thread of the message the current turn answers;
|
|
130
|
+
* `null` posts top-level.
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
replyThreadTs?: string | null;
|
|
134
|
+
/**
|
|
135
|
+
* In a code channel, the thread of the latest delivered message, which the
|
|
136
|
+
* next turn replies in.
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
139
|
+
pendingReplyThreadTs?: string | null;
|
|
108
140
|
/** Slack team id, when the inbound event carried one. */
|
|
109
141
|
teamId: string | null;
|
|
110
142
|
/** Slack message ts that triggered the active turn. */
|
|
@@ -137,7 +169,13 @@ export interface SlackChannelState extends UndeliveredReplyState {
|
|
|
137
169
|
* resolution outcome.
|
|
138
170
|
*/
|
|
139
171
|
pendingAuthMessageTs?: Record<string, string>;
|
|
140
|
-
|
|
172
|
+
/**
|
|
173
|
+
* The messages holding open or recently answered approvals, questions, and budget prompts.
|
|
174
|
+
* Each is updated as its requests change and forgotten once all of them settle. eve ignores
|
|
175
|
+
* the `pendingApprovalCards` field that earlier versions wrote, so cards posted before an
|
|
176
|
+
* upgrade keep their buttons.
|
|
177
|
+
*/
|
|
178
|
+
interactionCards?: readonly SlackInteractionCardRecord[];
|
|
141
179
|
/**
|
|
142
180
|
* Each recent turn's tool calls and the task card eve last wrote for it, by
|
|
143
181
|
* turn id. The default `turn.paused` handler names the working tasks.
|
|
@@ -177,6 +215,8 @@ export interface SlackInstrumentationMetadata extends Record<string, unknown> {
|
|
|
177
215
|
readonly teamId: string | null;
|
|
178
216
|
readonly threadTs: string | null;
|
|
179
217
|
readonly triggeringUserId: string | null;
|
|
218
|
+
/** `true` in a code channel session; absent elsewhere. */
|
|
219
|
+
readonly codeChannel?: true;
|
|
180
220
|
}
|
|
181
221
|
/**
|
|
182
222
|
* Slack channel credentials: outbound bot token plus inbound webhook
|
|
@@ -224,6 +264,24 @@ export interface SlackReceiveTarget {
|
|
|
224
264
|
* exclusive with {@link threadTs}.
|
|
225
265
|
*/
|
|
226
266
|
readonly initialMessage?: SlackInitialMessage;
|
|
267
|
+
/**
|
|
268
|
+
* Opens a Slack code channel from a message in {@link channelId} and starts
|
|
269
|
+
* the session there, as the code channel's one session. Slack links the
|
|
270
|
+
* channel and the origin message to each other. One requester gets one code
|
|
271
|
+
* channel per origin message, so a repeated send joins that channel's
|
|
272
|
+
* session. Requires `codeChannels: true`. Mutually exclusive with
|
|
273
|
+
* {@link threadTs} and {@link initialMessage}.
|
|
274
|
+
*/
|
|
275
|
+
readonly codeChannel?: SlackCodeChannelTarget;
|
|
276
|
+
}
|
|
277
|
+
/** The code channel `slackChannel().receive` opens for `target.codeChannel`. */
|
|
278
|
+
export interface SlackCodeChannelTarget {
|
|
279
|
+
/** Channel name shown in Slack. Omit it to let Slack name the channel from the message. */
|
|
280
|
+
readonly name?: string;
|
|
281
|
+
/** ts of the message the code channel continues. */
|
|
282
|
+
readonly originTs: string;
|
|
283
|
+
/** Thread of the origin message: its own ts when top-level. */
|
|
284
|
+
readonly originThreadTs: string;
|
|
227
285
|
}
|
|
228
286
|
/**
|
|
229
287
|
* Pre-agent post issued by `slackChannel().receive` when the caller
|
|
@@ -237,14 +295,9 @@ export interface SlackInitialMessage {
|
|
|
237
295
|
/**
|
|
238
296
|
* Options for one turn requested by a generic Slack event handler.
|
|
239
297
|
*/
|
|
240
|
-
export
|
|
241
|
-
readonly auth: SessionAuthContext | null;
|
|
242
|
-
/** Prior conversation added as user and assistant turns before the message. */
|
|
243
|
-
readonly history?: readonly SessionHistoryMessage[];
|
|
298
|
+
export type SlackEventSendOptions = SessionSendOptions & {
|
|
244
299
|
readonly target: SlackReceiveTarget;
|
|
245
|
-
|
|
246
|
-
readonly title?: string;
|
|
247
|
-
}
|
|
300
|
+
};
|
|
248
301
|
/** Options for answering pending input requests from a generic Slack event handler. */
|
|
249
302
|
export interface SlackEventRespondOptions {
|
|
250
303
|
readonly auth: SessionAuthContext | null;
|
|
@@ -258,12 +311,18 @@ export type SlackEventSendFn = (message: MessageContent, options: SlackEventSend
|
|
|
258
311
|
/** Answers pending input requests on one Slack thread. */
|
|
259
312
|
export type SlackEventRespondFn = <const TResponses extends readonly Answer[]>(answers: StrictAnswers<TResponses>, options: SlackEventRespondOptions) => ReturnType<SlackSessionOperations["respond"]>;
|
|
260
313
|
/**
|
|
261
|
-
* Slack
|
|
314
|
+
* Slack session identity used by workspace-scoped inbound helpers: a thread,
|
|
315
|
+
* or a code channel, whose one session spans all of its threads.
|
|
262
316
|
*/
|
|
263
|
-
export
|
|
317
|
+
export type SlackSessionTarget = {
|
|
264
318
|
readonly channelId: string;
|
|
265
319
|
readonly threadTs: string;
|
|
266
|
-
|
|
320
|
+
readonly codeChannel?: never;
|
|
321
|
+
} | {
|
|
322
|
+
readonly channelId: string;
|
|
323
|
+
readonly codeChannel: true;
|
|
324
|
+
readonly threadTs?: never;
|
|
325
|
+
};
|
|
267
326
|
/**
|
|
268
327
|
* Imperative surface handed to `slackChannel({ onEvent })`. Generic Events API
|
|
269
328
|
* payloads are not necessarily tied to one thread, so the context exposes a
|
|
@@ -318,6 +377,25 @@ export interface SlackInboundMessageContext extends SlackContext, SlackSessionOp
|
|
|
318
377
|
isSubscribed(): Promise<boolean>;
|
|
319
378
|
/** Returns whether the inbound event explicitly mentions this bot. */
|
|
320
379
|
isBotMentioned(): boolean;
|
|
380
|
+
/**
|
|
381
|
+
* Opens a Slack code channel from this message and returns session
|
|
382
|
+
* operations bound to the channel's one session. Slack links the channel
|
|
383
|
+
* in this thread. Send the first turn with the returned `send`, then return
|
|
384
|
+
* `null` to give the thread no turn. One author gets one code channel per
|
|
385
|
+
* message, so calling it again returns the same channel. Requires
|
|
386
|
+
* `codeChannels: true`, and throws for a message already in a code channel.
|
|
387
|
+
*/
|
|
388
|
+
openCodeChannel(options?: SlackOpenCodeChannelOptions): Promise<SlackOpenedCodeChannel>;
|
|
389
|
+
}
|
|
390
|
+
/** Options for `ctx.openCodeChannel()`. */
|
|
391
|
+
export interface SlackOpenCodeChannelOptions {
|
|
392
|
+
/** Channel name shown in Slack. Omit to let Slack name it from the message. */
|
|
393
|
+
readonly name?: string;
|
|
394
|
+
}
|
|
395
|
+
/** A code channel opened by `ctx.openCodeChannel()`, bound to its one session. */
|
|
396
|
+
export interface SlackOpenedCodeChannel extends SlackSessionOperations {
|
|
397
|
+
/** Slack channel id of the new code channel. */
|
|
398
|
+
readonly channelId: string;
|
|
321
399
|
}
|
|
322
400
|
/** Interaction-scoped context handed to `slackChannel({ onInteraction })`. */
|
|
323
401
|
export interface SlackInteractionContext extends SlackContext, SlackSessionOperations {
|
|
@@ -488,6 +566,15 @@ export interface SlackChannelConfig {
|
|
|
488
566
|
readonly route?: string;
|
|
489
567
|
/** Policy for accepted messages that arrive while a turn is active. */
|
|
490
568
|
readonly turnPolicy?: TurnPolicy;
|
|
569
|
+
/**
|
|
570
|
+
* Serves each Slack code channel as one session and answers every message in
|
|
571
|
+
* it, not just mentions. Un-mentioned messages go to `onMessage`, else
|
|
572
|
+
* `onAppMention` (where `ctx.isBotMentioned()` returns `false`), else the
|
|
573
|
+
* default handler. Adds the code channel scopes, events, and feature flag to
|
|
574
|
+
* the app manifest. Without it, eve treats a code channel like any other
|
|
575
|
+
* channel and never looks one up.
|
|
576
|
+
*/
|
|
577
|
+
readonly codeChannels?: boolean;
|
|
491
578
|
/**
|
|
492
579
|
* Inbound upload policy applied to file attachments before they reach
|
|
493
580
|
* the harness. Violating attachments are dropped with a warning so the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{markEventHandled}from"./utils.js";import{attachInputText}from"#internal/input-text.js";import{createLogger,logError}from"#internal/logging.js";import{normalizeChannelAudience}from"#shared/channel-audience.js";import{setChannelBuildMetadata}from"#channel/compiled-channel.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{maxBytesOf}from"#internal/attachments/limited-read.js";import{withUndeliveredReplyNote}from"#public/channels/undelivered-reply.js";import{buildSlackBinding,buildSlackWorkspaceHandle,fetchSlackThreadWindow,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{resolveSlackTransportOptions}from"#public/channels/slack/transport.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackEventInstallationTeamId,slackEventReceivingBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{withFinalReplyDelivery}from"#public/channels/slack/reply-delivery.js";import{defaultEvents,defaultInteractionOpenedHandler,defaultOnMessage,defaultReceived}from"#public/channels/slack/defaults.js";import{composeSlackRenderers}from"#public/channels/slack/renderers.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{bindSlackSessionOperations,withSlackResponder}from"#public/channels/slack/session-operations.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{renderDefaultSlackTaskCard,withTaskCards}from"#public/channels/slack/task-card.js";import{formatSlackInboundMessage,formatSlackThreadContext,slackThreadSessionHistory}from"#public/channels/slack/model-context.js";import{isPrivateSlackConversation}from"#public/channels/slack/privacy.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{defineSlackAppManifest}from"#public/channels/slack/app-manifest.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,r){let{thread:i,slack:a}=buildSlackBinding({api:n,botToken:r?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,installationTeamId:e.installationTeamId??void 0,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.continuation?.alias(slackContinuationToken({channelId:e.channelId,threadTs:n}))},onPosted(){e.postedInThread=!0}});return{thread:i,slack:a,state:e}}function slackChannel(e={}){let t=resolveSlackTransportOptions(e.api),n=mergeUploadPolicy(e.uploadPolicy),r=createSlackFetchFile({api:t,botToken:e.credentials?.botToken,maxBytes:maxBytesOf(n)}),i=e.onAnswers??defaultOnAnswers,a=composeSlackRenderers(e.renderers??[],{events:{...defaultEvents,"interaction.opened":defaultInteractionOpenedHandler(e.approvalChannel)},received:defaultReceived,taskCard:renderDefaultSlackTaskCard}),o=a.events[`turn.started`],s=withTaskCards({...a.events,"content.completed":withFinalReplyDelivery(a.events[`content.completed`]),async"turn.started"(e,t){let{channel:n}=t,r=slackUserIdFromAuthContext(t.session.auth.current);r!==void 0&&(n.state.triggeringUserId=r),await o?.(e,t)}},a.taskCard),c=new Set,l=defineChannel({turnPolicy:e.turnPolicy,uploadPolicy:e.uploadPolicy,state:{channelId:null,threadTs:null,teamId:null,installationTeamId:null,triggeringUserId:null,triggeringMessageTs:null,pendingToolCallMessage:null,pendingAuthMessageTs:{},pendingApprovalCards:{},slackUsersByPrincipal:{}},fetchFile:r,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},audience:({state:e})=>e.audience??`unknown`,context(n,r){return rebuildSlackContext(n,r,t,e.credentials)},deliver(e,t){return recordSlackPrincipal(t.state,t.session.auth.current,e),withUndeliveredReplyNote(t.state,e,`Slack`)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(r,o)=>{let{waitUntil:s}=o,{from:l,resolveSession:u}=o,d=await verifyInbound(r,e.credentials);return d===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(r.headers)?new Response(`ok`):(r.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(d,{from:l,resolveSession:u,waitUntil:s},{api:t,config:e,onAnswers:i}):handleEventPost({api:t,body:d,from:l,resolveSession:u,waitUntil:s,config:e,received:a.received,uploadPolicy:n,handledEvents:c,headers:r.headers})})],receive(n,{from:r}){return receiveOnSlack(n,{from:r,api:t,credentials:e.credentials})},events:s}),u=e.credentials,d=defineSlackAppManifest({botName:e.botName});return setChannelBuildMetadata(l,e=>({externalCredentials:u?.vercelConnect,manifest:d.build(e)})),l}function defaultOnAnswers(e){return{auth:e.defaultAuth}}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=typeof n.installationTeamId==`string`?n.installationTeamId:t.installationTeamId,o=n.initialMessage;if(o&&i.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let s=i;if(o){let{thread:e}=buildSlackBinding({api:t.api,botToken:t.credentials?.botToken,channelId:r,threadTs:``,installationTeamId:a,teamId:t.teamId}),n={card:o.card};o.fallbackText!==void 0&&(n.fallbackText=o.fallbackText),s=(await e.post(n)).id}let c=s||crypto.randomUUID(),l=n.audience===void 0?void 0:normalizeChannelAudience(n.audience),u={channelId:r,installationTeamId:a??null,threadTs:s||null,teamId:t.teamId??null,triggeringUserId:t.triggeringUserId??null};return l!==void 0&&(u.audience=l),t.from(slackContinuationToken({channelId:r,threadTs:c})).send(e.message,{auth:e.auth,history:e.history,state:u,title:e.title})}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(e){let{config:t}=e,n,r;try{n=parseSlackWebhookBody(e.body,{headers:e.headers}),r=parseSlackEventEnvelope(e.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(n.kind===`url_verification`)return new Response(n.challenge,{status:200,headers:{"content-type":`text/plain`}});if(r===null)return new Response(`ok`);let i=typeof r.api_app_id==`string`?r.api_app_id:void 0,a=slackEventBotUserId(r),o=slackEventReceivingBotUserId(r),s=slackEventInstallationTeamId(r),dispatch=null,c=null,dispatchMessage=(n,r,c)=>()=>dispatchSlackMessage({api:e.api,appId:i,botUserId:a,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:c,installationTeamId:s,kind:n,message:r,received:e.received,resolvesTypedInput:t.onAnswers===void 0&&t.approvalChannel===void 0,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy});if(n.kind===`app_mention`||n.kind===`direct_message`){let e=n.kind,r=slackMessageFromWebhookPayload(n,s);if(r!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},r)){let n=(e===`app_mention`?t.onAppMention:t.onDirectMessage)??t.onMessage;n===void 0?c=dispatchMessage(e,r,defaultOnMessage):dispatch=dispatchMessage(e,r,n)}}let l=t.onMessage;if(dispatch===null&&l!==void 0){let e=parseMessageEvent(r);e!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},e)&&(a===void 0||!e.text.includes(`<@${a}`))&&(dispatch=dispatchMessage(`channel_message`,e,l))}let u=t.onEvent;if(dispatch===null&&u!==void 0&&(dispatch=()=>dispatchSlackEvent({api:e.api,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,envelope:r,handler:u,installationTeamId:s})),dispatch??=c,dispatch===null)return new Response(`ok`);let d=r.event_id;if(d){if(e.handledEvents.has(d))return log.warn(`received a duplicate event`,{event_id:d,event_time:r.event_time,retry_num:n.retry?.num??`(null)`,retry_reason:n.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(d,e.handledEvents)}return e.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({channelId:e.message.channelId,threadTs:e.message.threadTs}),{thread:n,slack:r}=buildSlackBinding({api:e.api,appId:e.appId,botToken:e.credentials?.botToken,botUserId:e.botUserId,channelId:e.message.channelId,threadTs:e.message.threadTs,threadHasReplies:e.message.threadTs!==e.message.ts,installationTeamId:e.installationTeamId,teamId:e.message.teamId}),i=e.message.author,a={channelId:e.message.channelId,installationTeamId:e.installationTeamId??null,teamId:e.message.teamId??null,threadTs:e.message.threadTs,triggeringMessageTs:e.message.ts,triggeringUserId:i?.userId??null},o=bindSlackSessionOperations({address:t,defaultAuth:i===void 0?null:buildSlackAuthContext({channelId:e.message.channelId,fullName:i.fullName,installationTeamId:e.installationTeamId,isBot:i.isBot,teamId:e.message.teamId,threadTs:e.message.threadTs,userId:i.userId,userName:i.userName}),from:e.from,resolveSession:e.resolveSession,state:a}),s,isDMOrPrivateChannel=()=>s??=isPrivateSlackConversation({channelId:e.message.channelId,raw:e.message.raw,request:r.request}),c=e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),l={...o,isBotMentioned:()=>c,isDMOrPrivateChannel,isSubscribed:async()=>await o.resolveSession()!==void 0,slack:r,thread:n},u=e.kind===`channel_message`?void 0:acknowledgeMessage(e.received,e.message,{slack:r,thread:n}),d;try{d=await e.handler(l,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId})}if(d==null){u!==void 0&&(await u,await n.startTyping());return}await(u??acknowledgeMessage(e.received,e.message,{slack:r,thread:n}));let f=await isDMOrPrivateChannel();a.audience=e.kind===`direct_message`||f?`private`:`public`,await deliverSlackMessage({botUserId:e.receivingBotUserId,credentials:e.credentials,kind:e.kind,isPrivateConversation:f,isMentioned:c,message:e.message,resolvesTypedInput:e.resolvesTypedInput,result:d,sessionOperations:o,thread:n,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function acknowledgeMessage(e,t,n){try{await e(t,n)}catch(e){logError(log,`received renderer failed`,e,{channelId:t.channelId})}}async function dispatchSlackEvent(e){let t=e.envelope.event.team_id,n=typeof t==`string`?t:typeof e.envelope.team_id==`string`?e.envelope.team_id:void 0,r=[],sourceFor=t=>e.from(slackContinuationToken({channelId:t.channelId,threadTs:t.threadTs})),i={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({channelId:t.channelId,threadTs:t.threadTs})),respond:(t,{auth:n,target:r})=>sourceFor(r).respond(t,{auth:n,state:withSlackResponder(void 0,typeof e.envelope.event.user==`string`?e.envelope.event.user:void 0)}),send:(t,{auth:r,history:i,target:a,title:o})=>receiveOnSlack({auth:r,history:i,message:t,target:a,title:o},{from:e.from,api:e.api,credentials:e.credentials,installationTeamId:e.installationTeamId,teamId:n,...typeof e.envelope.event.user==`string`?{triggeringUserId:e.envelope.event.user}:{}}),slack:buildSlackWorkspaceHandle({api:e.api,botToken:e.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:n}),waitUntil(e){r.push(e)}};try{await e.handler(i,e.envelope.event)}catch(t){logError(log,`event handler failed`,t,{eventId:e.envelope.event_id,eventType:e.envelope.event.type})}await Promise.allSettled(r)}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:n}=e;try{let r=e.threadContext===void 0?[]:await loadThreadContextMessages(n,t,e.threadContext),i=r.length>0&&e.result.history===void 0&&await e.sessionOperations.resolveSession()===void 0,a=i?void 0:formatSlackThreadContext(r),o=e.result.history??(i?slackThreadSessionHistory(r):void 0),s=await collectInboundFileParts({botUserId:e.botUserId,fetchThreadWindow:(e,t)=>fetchSlackThreadWindow(n,e,t),isMentioned:e.kind===`app_mention`,mention:t,thread:n,policy:e.uploadPolicy}),c={botUserId:e.botUserId,channelId:t.channelId,fullName:t.author?.fullName,isMentioned:e.isMentioned,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},l=formatSlackInboundMessage(c,t),u=buildSlackTurnMessage(a===void 0?l:`${a}\n\n${l}`,s),d=e.result.context??[],f=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,p=e.resolvesTypedInput?slackTypedText(t.text,e.botUserId):``,m=attachInputText({auth:e.result.auth,title:f,...d.length>0&&{context:d},...o!==void 0&&{history:o}},s.length===0?p:void 0);await e.sessionOperations.send(u,m)}catch(n){logError(log,`${e.kind} delivery failed`,n,{channelId:t.channelId})}}function slackTypedText(e,t){return t===void 0?e:e.replace(RegExp(`<@${t}(?:\\|[^>]*)?>`,`gu`),``).trim()}function recordSlackPrincipal(e,t,n){if(t===null)return;let r=n.state?.triggeringUserId,i=slackUserIdFromAuthContext(t)??(typeof r==`string`?r:void 0);i!==void 0&&(e.slackUsersByPrincipal={...e.slackUsersByPrincipal,[t.principalId]:i})}export{slackChannel};
|
|
1
|
+
import{markEventHandled}from"./utils.js";import{createHash}from"node:crypto";import{toSessionSendOptions}from"#channel/session.js";import{attachInputText}from"#internal/input-text.js";import{createLogger,logError}from"#internal/logging.js";import{normalizeChannelAudience}from"#shared/channel-audience.js";import{SessionNotActiveError}from"#channel/session-not-active-error.js";import{setChannelBuildMetadata}from"#channel/compiled-channel.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{maxBytesOf}from"#internal/attachments/limited-read.js";import{withUndeliveredReplyNote}from"#public/channels/undelivered-reply.js";import{buildSlackBinding,buildSlackWorkspaceHandle,fetchSlackThreadWindow,slackCodeChannelContinuationToken,slackContinuationToken,slackSessionToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{createSlackCodeChannel,isSlackCodeChannel,isSlackCodeChannelOriginContext,setSlackCodeChannelStatus}from"#public/channels/slack/code-channel.js";import{resolveSlackTransportOptions}from"#public/channels/slack/transport.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackEventInstallationTeamId,slackEventReceivingBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{withFinalReplyDelivery}from"#public/channels/slack/reply-delivery.js";import{defaultEvents,defaultInteractionEvents,defaultOnMessage,defaultReceived}from"#public/channels/slack/defaults.js";import{composeInteractionCards,composeSlackRenderers}from"#public/channels/slack/renderers.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{bindSlackSessionOperations,withSlackResponder}from"#public/channels/slack/session-operations.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{renderDefaultSlackTaskCard,withTaskCards}from"#public/channels/slack/task-card.js";import{formatSlackInboundMessage,formatSlackThreadContext,slackThreadSessionHistory}from"#public/channels/slack/model-context.js";import{isPrivateSlackConversation}from"#public/channels/slack/privacy.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{defineSlackAppManifest}from"#public/channels/slack/app-manifest.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,r){let{thread:i,slack:a}=buildSlackBinding({api:n,botToken:r?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,codeChannel:e.codeChannel===!0,replyThreadTs:()=>e.replyThreadTs??void 0,installationTeamId:e.installationTeamId??void 0,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.continuation?.alias(slackContinuationToken({channelId:e.channelId,threadTs:n}))},onPosted(){e.postedInThread=!0}});return{thread:i,slack:a,state:e}}function slackChannel(e={}){let t=resolveSlackTransportOptions(e.api),n=mergeUploadPolicy(e.uploadPolicy),r=createSlackFetchFile({api:t,botToken:e.credentials?.botToken,maxBytes:maxBytesOf(n)}),i=e.onAnswers??defaultOnAnswers,a=composeInteractionCards(e.renderers??[]),o=composeSlackRenderers(e.renderers??[],{events:{...defaultEvents,...defaultInteractionEvents(e.approvalChannel,a)},received:defaultReceived,taskCard:renderDefaultSlackTaskCard}),s=o.events[`turn.started`],c=withTaskCards({...o.events,"content.completed":withFinalReplyDelivery(o.events[`content.completed`]),async"turn.started"(e,t){let{channel:n}=t,r=slackUserIdFromAuthContext(t.session.auth.current);r!==void 0&&(n.state.triggeringUserId=r),n.state.pendingReplyThreadTs!==void 0&&(n.state.replyThreadTs=n.state.pendingReplyThreadTs,n.state.pendingReplyThreadTs=void 0),await s?.(e,t)}},o.taskCard),l=new Set,u=defineChannel({turnPolicy:e.turnPolicy,uploadPolicy:e.uploadPolicy,state:{channelId:null,threadTs:null,teamId:null,installationTeamId:null,triggeringUserId:null,triggeringMessageTs:null,pendingToolCallMessage:null,pendingAuthMessageTs:{},interactionCards:[],slackUsersByPrincipal:{}},fetchFile:r,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null,installationTeamId:e.installationTeamId??null,triggeringMessageTs:e.triggeringMessageTs??null,...e.codeChannel===!0&&{codeChannel:!0}}},audience:({state:e})=>e.audience??`unknown`,context(n,r){return rebuildSlackContext(n,r,t,e.credentials)},deliver(e,t){return recordSlackPrincipal(t.state,t.session.auth.current,e),followReplyThread(t.state,e),followTriggeringMessage(t.state,e),withUndeliveredReplyNote(t.state,e,`Slack`)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(r,a)=>{let{waitUntil:s}=a,{from:c,resolveSession:u}=a,d=await verifyInbound(r,e.credentials);return d===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(r.headers)?new Response(`ok`):(r.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(d,{from:c,resolveSession:u,waitUntil:s},{api:t,config:e,onAnswers:i}):handleEventPost({api:t,body:d,from:c,resolveSession:u,waitUntil:s,config:e,received:o.received,uploadPolicy:n,handledEvents:l,headers:r.headers})})],receive(n,{from:r}){return receiveOnSlack(n,{from:r,api:t,codeChannels:e.codeChannels===!0,credentials:e.credentials})},events:c}),d=e.credentials,f=defineSlackAppManifest({botName:e.botName,codeChannels:e.codeChannels===!0});return setChannelBuildMetadata(u,e=>({externalCredentials:d?.vercelConnect,manifest:f.build(e)})),u}function defaultOnAnswers(e){return{auth:e.defaultAuth}}async function receiveOnSlack(e,t){let{message:n,target:r,...i}=e,a=r,o=a.channelId;if(!o||typeof o!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let s=typeof a.threadTs==`string`?a.threadTs:``,c=typeof a.installationTeamId==`string`?a.installationTeamId:t.installationTeamId,l=a.initialMessage;if(l&&s.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");if(a.codeChannel!==void 0){if(!t.codeChannels)throw Error("slackChannel().receive: set `codeChannels: true` on slackChannel() to open code channels.");if(l||s.length>0)throw Error("slackChannel().receive: `codeChannel` can't be combined with `threadTs` or `initialMessage`.");return await receiveInNewCodeChannel(e,t,{codeChannel:a.codeChannel,installationTeamId:c,originChannelId:o})}let u=s;if(l){let{thread:e}=buildSlackBinding({api:t.api,botToken:t.credentials?.botToken,channelId:o,threadTs:``,installationTeamId:c,teamId:t.teamId}),n={card:l.card};l.fallbackText!==void 0&&(n.fallbackText=l.fallbackText),u=(await e.post(n)).id}let d=u||(i.idempotencyKey===void 0?crypto.randomUUID():threadlessIdentityFromKey(i.idempotencyKey)),f=a.audience===void 0?void 0:normalizeChannelAudience(a.audience),p={channelId:o,installationTeamId:c??null,threadTs:u||null,teamId:t.teamId??null,triggeringUserId:t.triggeringUserId??null};return f!==void 0&&(p.audience=f),t.from(slackContinuationToken({channelId:o,threadTs:d})).send(n,{...i,state:p})}function threadlessIdentityFromKey(e){return`key-${createHash(`sha256`).update(e).digest(`hex`).slice(0,32)}`}async function receiveInNewCodeChannel(e,t,n){let{codeChannel:r,installationTeamId:i,originChannelId:a}=n,{request:o}=buildSlackWorkspaceHandle({api:t.api,botToken:t.credentials?.botToken,installationTeamId:i,teamId:t.teamId}),s=await openSlackCodeChannel({auth:e.auth,installationTeamId:i,name:r.name,originChannelId:a,originTs:r.originTs,request:o,teamId:t.teamId,triggeringUserId:t.triggeringUserId??slackUserIdFromAuthContext(e.auth)}),{message:c,target:l,...u}=e;return await t.from(s.address).send(c,{...u,auth:s.auth,state:s.state})}async function openSlackCodeChannel(e){let{channelId:t,isPrivate:n}=await createSlackCodeChannel({name:e.name??``,originChannelId:e.originChannelId,originTs:e.originTs,requester:e.auth?.principalId??`anonymous`,request:e.request});return{address:slackCodeChannelContinuationToken(t),auth:codeChannelAuth(e.auth,t),channelId:t,state:{audience:n?`private`:`public`,channelId:t,codeChannel:!0,installationTeamId:e.installationTeamId??null,replyThreadTs:null,teamId:e.teamId??null,threadTs:null,triggeringUserId:e.triggeringUserId??null}}}function codeChannelAuth(e,t){if(e?.authenticator!==`slack-webhook`)return e;let{thread_ts:n,...r}=e.attributes;return{...e,attributes:{...r,channel_id:t}}}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(e){let{config:t}=e,n,r;try{n=parseSlackWebhookBody(e.body,{headers:e.headers}),r=parseSlackEventEnvelope(e.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(n.kind===`url_verification`)return new Response(n.challenge,{status:200,headers:{"content-type":`text/plain`}});if(r===null)return new Response(`ok`);let i=typeof r.api_app_id==`string`?r.api_app_id:void 0,a=slackEventBotUserId(r),o=slackEventReceivingBotUserId(r),s=slackEventInstallationTeamId(r),c=r.event,l=c.channel;if(t.codeChannels===!0&&c.type===`agent_session_stopped`&&typeof l==`string`){if(isDuplicateEvent(r,n.retry,e.handledEvents))return new Response(`ok`);let i=buildSlackWorkspaceHandle({api:e.api,botToken:t.credentials?.botToken,installationTeamId:s,teamId:typeof r.team_id==`string`?r.team_id:void 0}).request,a=t.onEvent;return e.waitUntil((async()=>{if(c.thread_ts!==void 0&&!await isSlackCodeChannel(l,i)){if(a===void 0)return;await dispatchSlackEvent({api:e.api,from:e.from,resolveSession:e.resolveSession,codeChannels:!0,credentials:t.credentials,envelope:r,handler:a,installationTeamId:s});return}await cancelledARunningTurn(e.from,l)||await setSlackCodeChannelStatus(i,l,`active`)})().catch(e=>{logError(log,`agent_session_stopped handling failed`,e,{channelId:l})})),new Response(`ok`)}let u=null,d=null,dispatchMessage=(n,r,c,l)=>()=>dispatchSlackMessage({api:e.api,appId:i,botUserId:a,codeChannelOnly:l,detectCodeChannels:t.codeChannels===!0,receivingBotUserId:o,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,handler:c,installationTeamId:s,kind:n,message:r,received:e.received,resolvesTypedInput:t.onAnswers===void 0&&t.approvalChannel===void 0,threadContext:t.threadContext,uploadPolicy:e.uploadPolicy});if(n.kind===`app_mention`||n.kind===`direct_message`){let e=n.kind,r=slackMessageFromWebhookPayload(n,s);if(r!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},r)){let n=(e===`app_mention`?t.onAppMention:t.onDirectMessage)??t.onMessage;n===void 0?d=dispatchMessage(e,r,defaultOnMessage):u=dispatchMessage(e,r,n)}}let f=t.onMessage;if(u===null&&f!==void 0){let e=parseMessageEvent(r);e!==null&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},e)&&(a===void 0||!e.text.includes(`<@${a}`))&&(u=dispatchMessage(`channel_message`,e,f))}let p=t.onEvent,m=p===void 0?void 0:()=>dispatchSlackEvent({api:e.api,from:e.from,resolveSession:e.resolveSession,credentials:t.credentials,codeChannels:t.codeChannels===!0,envelope:r,handler:p,installationTeamId:s});if(u===null&&d===null&&t.codeChannels===!0){let e=parseMessageEvent(r);if(e!==null&&e.author?.isBot===!1&&!isSelfAuthoredSlackMessage({appId:i,botUserId:a},e)&&!isSlackCodeChannelOriginContext(e.raw)&&(a===void 0||!e.text.includes(`<@${a}`))){let n=t.onAppMention;n===void 0?d=dispatchMessage(`channel_message`,e,defaultOnMessage,{}):u=dispatchMessage(`channel_message`,e,n,{otherwise:m})}}return u??=m??d,u===null||isDuplicateEvent(r,n.retry,e.handledEvents)||e.waitUntil(u()),new Response(`ok`)}function isDuplicateEvent(e,t,n){let r=e.event_id;return r?n.has(r)?(log.warn(`received a duplicate event`,{event_id:r,event_time:e.event_time,retry_num:t?.num??`(null)`,retry_reason:t?.reason??`(null)`}),!0):(markEventHandled(r,n),!1):!1}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=e.message.author,n=buildSlackWorkspaceHandle({api:e.api,botToken:e.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:e.message.teamId}).request,r,conversationRequest=(e,t)=>e===`conversations.info`?r??=n(e,t):n(e,t),i=e.detectCodeChannels&&e.kind!==`direct_message`&&await isSlackCodeChannel(e.message.channelId,conversationRequest);if(!i&&e.codeChannelOnly!==void 0){await e.codeChannelOnly.otherwise?.();return}let a=i?``:e.message.threadTs,o=e.message.threadTs!==e.message.ts,s=i&&o?e.message.threadTs:null,c=i?slackCodeChannelContinuationToken(e.message.channelId):slackContinuationToken({channelId:e.message.channelId,threadTs:a}),{thread:l,slack:u}=buildSlackBinding({api:e.api,appId:e.appId,botToken:e.credentials?.botToken,botUserId:e.botUserId,channelId:e.message.channelId,threadTs:a,codeChannel:i,replyThreadTs:()=>s??void 0,threadHasReplies:o,installationTeamId:e.installationTeamId,teamId:e.message.teamId}),d={channelId:e.message.channelId,installationTeamId:e.installationTeamId??null,teamId:e.message.teamId??null,threadTs:a||null,triggeringMessageTs:e.message.ts,triggeringUserId:t?.userId??null,...i&&{codeChannel:!0,replyThreadTs:s}},f=t===void 0?null:buildSlackAuthContext({channelId:e.message.channelId,fullName:t.fullName,installationTeamId:e.installationTeamId,isBot:t.isBot,teamId:e.message.teamId,threadTs:a,userId:t.userId,userName:t.userName}),p=bindSlackSessionOperations({address:c,defaultAuth:f,from:e.from,resolveSession:e.resolveSession,state:d}),m,isDMOrPrivateChannel=()=>m??=isPrivateSlackConversation({channelId:e.message.channelId,raw:e.message.raw,request:conversationRequest}),openCodeChannel=async(t={})=>{if(!e.detectCodeChannels)throw Error("ctx.openCodeChannel(): set `codeChannels: true` on slackChannel() to open code channels.");if(i)throw Error(`ctx.openCodeChannel(): this message is already in a code channel.`);return await openCodeChannelFromMessage({defaultAuth:f,from:e.from,installationTeamId:e.installationTeamId,message:e.message,name:t.name,request:n,resolveSession:e.resolveSession})},h=e.kind===`app_mention`||e.botUserId!==void 0&&e.message.text.includes(`<@${e.botUserId}`),g={...p,isBotMentioned:()=>h,isDMOrPrivateChannel,isSubscribed:async()=>await p.resolveSession()!==void 0,openCodeChannel,slack:u,thread:l},_=e.kind!==`channel_message`&&!i?acknowledgeMessage(e.received,e.message,{slack:u,thread:l}):void 0,v;try{v=await e.handler(g,e.message)}catch(t){logError(log,`${e.kind} handler failed`,t,{channelId:e.message.channelId})}if(v==null){_!==void 0&&(await _,await l.startTyping());return}await(_??acknowledgeMessage(e.received,e.message,{slack:u,thread:l}));let y=await isDMOrPrivateChannel();d.audience=e.kind===`direct_message`||y?`private`:`public`,await deliverSlackMessage({botUserId:e.receivingBotUserId,credentials:e.credentials,kind:e.kind,isPrivateConversation:y,isMentioned:h,message:e.message,resolvesTypedInput:e.resolvesTypedInput,result:v,sessionOperations:p,thread:l,threadContext:e.threadContext,uploadPolicy:e.uploadPolicy})}async function openCodeChannelFromMessage(e){let t=await openSlackCodeChannel({auth:e.defaultAuth,installationTeamId:e.installationTeamId,name:e.name,originChannelId:e.message.channelId,originTs:e.message.ts,request:e.request,teamId:e.message.teamId,triggeringUserId:e.message.author?.userId}),n=bindSlackSessionOperations({address:t.address,defaultAuth:t.auth,from:e.from,resolveSession:e.resolveSession,state:t.state}),moved=e=>e?.auth?{...e,auth:codeChannelAuth(e.auth,t.channelId)}:e;return{...n,channelId:t.channelId,send:(e,t)=>n.send(e,moved(t)),respond:(e,t)=>n.respond(e,moved(t))}}async function acknowledgeMessage(e,t,n){try{await e(t,n)}catch(e){logError(log,`received renderer failed`,e,{channelId:t.channelId})}}async function dispatchSlackEvent(e){let t=e.envelope.event.team_id,n=typeof t==`string`?t:typeof e.envelope.team_id==`string`?e.envelope.team_id:void 0,r=[],sourceFor=t=>e.from(slackSessionToken(t)),i={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(slackSessionToken(t)),respond:(t,{auth:n,target:r})=>sourceFor(r).respond(t,{auth:n,state:withSlackResponder(void 0,typeof e.envelope.event.user==`string`?e.envelope.event.user:void 0)}),send:async(t,r)=>await receiveOnSlack({...toSessionSendOptions(r),target:r.target,message:t},{from:e.from,api:e.api,codeChannels:e.codeChannels,credentials:e.credentials,installationTeamId:e.installationTeamId,teamId:n,...typeof e.envelope.event.user==`string`?{triggeringUserId:e.envelope.event.user}:{}}),slack:buildSlackWorkspaceHandle({api:e.api,botToken:e.credentials?.botToken,installationTeamId:e.installationTeamId,teamId:n}),waitUntil(e){r.push(e)}};try{await e.handler(i,e.envelope.event)}catch(t){logError(log,`event handler failed`,t,{eventId:e.envelope.event_id,eventType:e.envelope.event.type})}await Promise.allSettled(r)}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:n}=e;try{let r=e.threadContext===void 0?[]:await loadThreadContextMessages(n,t,e.threadContext),i=r.length>0&&e.result.history===void 0&&await e.sessionOperations.resolveSession()===void 0,a=i?void 0:formatSlackThreadContext(r),o=e.result.history??(i?slackThreadSessionHistory(r):void 0),s=await collectInboundFileParts({botUserId:e.botUserId,fetchThreadWindow:(e,t)=>fetchSlackThreadWindow(n,e,t),isMentioned:e.kind===`app_mention`,mention:t,thread:n,policy:e.uploadPolicy}),c={botUserId:e.botUserId,channelId:t.channelId,fullName:t.author?.fullName,isMentioned:e.isMentioned,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},l=formatSlackInboundMessage(c,t),u=buildSlackTurnMessage(a===void 0?l:`${a}\n\n${l}`,s),d=e.result.context??[],f=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,p=e.resolvesTypedInput?slackTypedText(t.text,e.botUserId):``,m=attachInputText({auth:e.result.auth,title:f,...d.length>0&&{context:d},...o!==void 0&&{history:o}},s.length===0?p:void 0);await e.sessionOperations.send(u,m)}catch(n){logError(log,`${e.kind} delivery failed`,n,{channelId:t.channelId})}}function slackTypedText(e,t){return t===void 0?e:e.replace(RegExp(`<@${t}(?:\\|[^>]*)?>`,`gu`),``).trim()}async function cancelledARunningTurn(e,t){try{let{outcome:n}=await(await e(slackCodeChannelContinuationToken(t)).cancel()).result({signal:AbortSignal.timeout(1e4)});return n!==`ignored`}catch(e){if(e instanceof SessionNotActiveError)return!1;throw e}}function followReplyThread(e,t){t.message!==void 0&&e.codeChannel===!0&&(e.pendingReplyThreadTs=t.state?.replyThreadTs??null)}function followTriggeringMessage(e,t){if(t.message===void 0)return;let n=t.state?.triggeringMessageTs;typeof n==`string`&&(e.triggeringMessageTs=n)}function recordSlackPrincipal(e,t,n){if(t===null)return;let r=n.state?.triggeringUserId,i=slackUserIdFromAuthContext(t)??(typeof r==`string`?r:void 0);i!==void 0&&(e.slackUsersByPrincipal={...e.slackUsersByPrincipal,[t.principalId]:i})}export{slackChannel};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TaskCard } from "#channel/task-card.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SlackCardMessage } from "#public/channels/slack/renderers.js";
|
|
3
3
|
import type { SlackChannelInternalEvents } from "#public/channels/slack/slackChannel.js";
|
|
4
4
|
/**
|
|
5
5
|
* eve's default task card: a `task_card` block for one task, or a `plan` block
|
|
@@ -8,7 +8,7 @@ import type { SlackChannelInternalEvents } from "#public/channels/slack/slackCha
|
|
|
8
8
|
* with `eve__task_cancel` shows as done, since it no longer needed the work; a
|
|
9
9
|
* failed task, or one stopped any other way, shows as an error.
|
|
10
10
|
*/
|
|
11
|
-
export declare function renderDefaultSlackTaskCard(snapshot: TaskCard):
|
|
11
|
+
export declare function renderDefaultSlackTaskCard(snapshot: TaskCard): SlackCardMessage | null;
|
|
12
12
|
/** The card message eve last wrote for a turn: the card that begins at `since`. */
|
|
13
13
|
export interface SlackTaskCardState {
|
|
14
14
|
readonly ts?: string;
|
|
@@ -21,4 +21,4 @@ export interface SlackTaskCardState {
|
|
|
21
21
|
* the renderer chain's own `committed`, so the card never depends on which handlers a renderer
|
|
22
22
|
* keeps, and writes each card `taskCard` returns if it changed.
|
|
23
23
|
*/
|
|
24
|
-
export declare function withTaskCards(events: SlackChannelInternalEvents, taskCard: (card: TaskCard) =>
|
|
24
|
+
export declare function withTaskCards(events: SlackChannelInternalEvents, taskCard: (card: TaskCard) => SlackCardMessage | null): SlackChannelInternalEvents;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash}from"node:crypto";import{callLabel,callOutputSource,callState,openInteractions}from"#protocol/session-projection/selectors.js";import{createLogger,logError}from"#internal/logging.js";import{normalizeChannelAudience}from"#shared/channel-audience.js";import{sessionTables}from"#protocol/session-projection/session-view.js";import{contextStorage}from"#context/container.js";import{ScheduleIdKey}from"#context/keys.js";import{displayName}from"#protocol/session-projection/display-name.js";import{AGENT_TOOL_NAME}from"#tools/framework/agent-contract.js";import{formatDuration}from"#shared/format-duration.js";import{firstSentence,normalizePresentationText}from"#shared/presentation-text.js";import{truncateMessageText}from"#public/channels/slack/limits.js";import{taskCards}from"#channel/task-card.js";import{restoreStatus}from"#public/channels/slack/thread-status.js";const log=createLogger(`slack.task-card`);function renderDefaultSlackTaskCard(e){let t=sessionTables(e.view),n={...e,view:t,calls:e.calls.flatMap(e=>t.calls[e.callId]??[])},r=n.calls.filter(e=>e.taskId!==void 0);if(r.length===0)return null;let{earlier:i,rows:a}=collapseEarlierRows(n.view,r),o=planTitle(n,r),s=[...i===void 0?[]:[i],...a.map(e=>toSlackTask(n,e))],c=slackBlockId(n.turnId),l=s.length===1?[{block_id:c,type:`task_card`,...s[0]}]:[{block_id:c,type:`plan`,tasks:s,title:o}],u=r.map(e=>callLabel(e)).join(`, `);return{blocks:l,text:truncateMessageText(`${o}: ${u}`)}}function planTitle(e,t){let{view:n}=e;if(e.state===`blocked`)return waitingText(t.flatMap(e=>openInteractions(n,{about:{callId:e.callId}}))[0]?.request.kind);let r=t.length,names=e=>r>1?agentNames(n,e):void 0;if(e.state===`working`){let e=t.filter(e=>e.status!==`settled`),n=r-e.length;if(n===0){let e=names(t);return e===void 0?`Working on ${countTasks(r)}`:`Asking ${e}`}let i=`${String(n)} of ${countTasks(r)} done`,a=names(e);return a===void 0?i:`Waiting on ${a} · ${i}`}let i=t.filter(e=>failedTask(n,e)).length,a=t.filter(e=>isInterrupted(n,e)).length,o=[];i>0&&o.push(`${String(i)} failed`),a>0&&o.push(`${String(a)} stopped`);let s=turnDuration(t),c=`Finished ${countTasks(r)}${s===void 0?``:` in ${s}`}`;return o.length===0?c:`${c}: ${o.join(`, `)}`}function agentNames(e,t){let agent=t=>(t.taskId===void 0?void 0:e.tasks[t.taskId]?.kind)??t.capability.kind;if(t.some(e=>agent(e)!==`agent`||e.capability.name===AGENT_TOOL_NAME))return;let n=[...new Set(t.map(e=>displayName(e.capability.name)))];if(!(n.length===0||n.length>3))return n.length<=2?n.join(` and `):`${n.slice(0,-1).join(`, `)}, and ${n.at(-1)}`}function turnDuration(e){let t=e.flatMap(e=>e.endedAt===void 0?[]:[Date.parse(e.endedAt)]);if(t.length===0)return;let n=Math.min(...e.map(e=>Date.parse(e.startedAt)));return shownDuration(Math.max(...t)-n)}function taskDuration(e){if(e.endedAt!==void 0)return shownDuration(Date.parse(e.endedAt)-Date.parse(e.startedAt))}function shownDuration(e){return Number.isFinite(e)&&e>=1e3?formatDuration(e):void 0}function failedTask(e,t){let n=callState(e,t.callId);return n===`failed`||n===`rejected`}function isInterrupted(e,t){return callState(e,t.callId)===`cancelled`&&t.reason!==`task-cancel`}function countTasks(e){return e===1?`1 task`:`${String(e)} tasks`}function collapseEarlierRows(e,t){if(t.length<=50)return{rows:t};let n=t.length-49,r=t.filter(e=>e.status===`settled`).slice(0,n),i=new Set(r.map(e=>e.callId)),a=t.filter(e=>!i.has(e.callId));return r.length===0?{rows:a.slice(-50)}:{earlier:{status:r.some(t=>failedTask(e,t)||isInterrupted(e,t))?`error`:`complete`,task_id:`earlier`,title:truncate(`${countTasks(r.length)} finished earlier`,80)},rows:a.slice(-49)}}function toSlackTask(e,t){let{view:n}=e,r={status:slackStatus(n,t),task_id:slackTaskId(t.callId),title:truncate(callLabel(t),80)},i=openInteractions(n,{about:{callId:t.callId}}).at(-1);i!==void 0&&(r.details=richText(blockerLine(e,i)));let a=outputLine(e,t);return a!==void 0&&(r.output=richText(a)),r}function slackStatus(e,t){switch(callState(e,t.callId)){case`completed`:return`complete`;case`cancelled`:return isInterrupted(e,t)?`error`:`complete`;case`failed`:case`rejected`:return`error`;default:return`in_progress`}}function outputLine(e,t){let{view:n}=e,r=taskDuration(t);switch(callState(n,t.callId)){case`completed`:{let e=brief(resultText(callOutputSource(n,t.callId)?.output??t.output));if(r===void 0)return e;let i=`Done in ${r}`;return e===void 0?i:`${i}: ${e}`}case`failed`:case`rejected`:{let n=r===void 0?`Failed`:`Failed after ${r}`,i=e.audience===`private`?brief(t.error?.message):void 0;return i===void 0?n:`${n}: ${i}`}case`cancelled`:return stoppedLine(t.reason);default:return}}function stoppedLine(e){switch(e){case`task-cancel`:return`Stopped early since it was no longer needed`;case`turn-cancelled`:return`Stopped by request`;case`turn-ended`:return`Stopped when the turn ended`;default:return`Stopped`}}function blockerLine(e,t){let{request:n}=t,r=waitingText(n.kind);if(e.audience!==`private`)return r;let i=n.kind===`sign-in`?n.signIn?.displayName??n.signIn?.name??n.prompt:n.prompt,a=i===void 0?``:normalizePresentationText(i);return a===``?r:`${r}: ${a.slice(0,200)}`}function waitingText(e){switch(e){case`approval`:return`Waiting for approval`;case`sign-in`:return`Waiting for sign-in`;default:return`Waiting for a response`}}function resultText(e){if(typeof e==`string`)return e;if(typeof e!=`object`||!e||Array.isArray(e))return;let t=e;return t.message??t.text??t.summary}function brief(e){return typeof e==`string`?firstSentence(e):void 0}function richText(e){return{elements:[{elements:[{text:truncate(e,100),type:`text`}],type:`rich_text_section`}],type:`rich_text`}}function truncate(e,t){if(e.length<=t)return e;let n=e.slice(0,t-1),r=n.lastIndexOf(` `);return`${(r>t/2?n.slice(0,r):n).trimEnd()}…`}function slackTaskId(e){return e.replace(/[^a-zA-Z0-9_-]/g,`_`).slice(-200)}function slackBlockId(e){return`eve_task_card_${slackTaskId(e)}`}function withTaskCards(e,t){let n=e.committed;return{...e,async committed(e,r){try{await n?.(e,r)}finally{let{channel:n,view:i}=r,a=new Set([...e.facts.flatMap(e=>e.scope?.turnId??[]),...Object.keys(n.state.taskCards??{})]);for(let e of a)await writeTaskCards(n,sessionTables(i),e,t)}}}}async function writeTaskCards(e,t,n,r){if(t.turns[n]===void 0)return;let i=normalizeChannelAudience(e.state.audience),a=taskCards(t,n,i),o=e.state.taskCards?.[n]?.since??0;for(let t of a)t.since<o||(t.since>(e.state.taskCards?.[n]?.since??0)&&rememberCard(e.state,n,{since:t.since}),await writeChangedCard(e,n,t,r));a.at(-1)?.state===`finished`&&forgetTurn(e.state,n)}async function writeChangedCard(e,t,n,r){let{channelId:i,
|
|
1
|
+
import{createHash}from"node:crypto";import{callLabel,callOutputSource,callState,openInteractions}from"#protocol/session-projection/selectors.js";import{createLogger,logError}from"#internal/logging.js";import{normalizeChannelAudience}from"#shared/channel-audience.js";import{sessionTables}from"#protocol/session-projection/session-view.js";import{contextStorage}from"#context/container.js";import{ScheduleIdKey}from"#context/keys.js";import{displayName}from"#protocol/session-projection/display-name.js";import{AGENT_TOOL_NAME}from"#tools/framework/agent-contract.js";import{formatDuration}from"#shared/format-duration.js";import{firstSentence,normalizePresentationText}from"#shared/presentation-text.js";import{slackThreadTsParam}from"#public/channels/slack/code-channel.js";import{truncateMessageText}from"#public/channels/slack/limits.js";import{taskCards}from"#channel/task-card.js";import{restoreStatus}from"#public/channels/slack/thread-status.js";const log=createLogger(`slack.task-card`);function renderDefaultSlackTaskCard(e){let t=sessionTables(e.view),n={...e,view:t,calls:e.calls.flatMap(e=>t.calls[e.callId]??[])},r=n.calls.filter(e=>e.taskId!==void 0);if(r.length===0)return null;let{earlier:i,rows:a}=collapseEarlierRows(n.view,r),o=planTitle(n,r),s=[...i===void 0?[]:[i],...a.map(e=>toSlackTask(n,e))],c=slackBlockId(n.turnId),l=s.length===1?[{block_id:c,type:`task_card`,...s[0]}]:[{block_id:c,type:`plan`,tasks:s,title:o}],u=r.map(e=>callLabel(e)).join(`, `);return{blocks:l,text:truncateMessageText(`${o}: ${u}`)}}function planTitle(e,t){let{view:n}=e;if(e.state===`blocked`)return waitingText(t.flatMap(e=>openInteractions(n,{about:{callId:e.callId}}))[0]?.request.kind);let r=t.length,names=e=>r>1?agentNames(n,e):void 0;if(e.state===`working`){let e=t.filter(e=>e.status!==`settled`),n=r-e.length;if(n===0){let e=names(t);return e===void 0?`Working on ${countTasks(r)}`:`Asking ${e}`}let i=`${String(n)} of ${countTasks(r)} done`,a=names(e);return a===void 0?i:`Waiting on ${a} · ${i}`}let i=t.filter(e=>failedTask(n,e)).length,a=t.filter(e=>isInterrupted(n,e)).length,o=[];i>0&&o.push(`${String(i)} failed`),a>0&&o.push(`${String(a)} stopped`);let s=turnDuration(t),c=`Finished ${countTasks(r)}${s===void 0?``:` in ${s}`}`;return o.length===0?c:`${c}: ${o.join(`, `)}`}function agentNames(e,t){let agent=t=>(t.taskId===void 0?void 0:e.tasks[t.taskId]?.kind)??t.capability.kind;if(t.some(e=>agent(e)!==`agent`||e.capability.name===AGENT_TOOL_NAME))return;let n=[...new Set(t.map(e=>displayName(e.capability.name)))];if(!(n.length===0||n.length>3))return n.length<=2?n.join(` and `):`${n.slice(0,-1).join(`, `)}, and ${n.at(-1)}`}function turnDuration(e){let t=e.flatMap(e=>e.endedAt===void 0?[]:[Date.parse(e.endedAt)]);if(t.length===0)return;let n=Math.min(...e.map(e=>Date.parse(e.startedAt)));return shownDuration(Math.max(...t)-n)}function taskDuration(e){if(e.endedAt!==void 0)return shownDuration(Date.parse(e.endedAt)-Date.parse(e.startedAt))}function shownDuration(e){return Number.isFinite(e)&&e>=1e3?formatDuration(e):void 0}function failedTask(e,t){let n=callState(e,t.callId);return n===`failed`||n===`rejected`}function isInterrupted(e,t){return callState(e,t.callId)===`cancelled`&&t.reason!==`task-cancel`}function countTasks(e){return e===1?`1 task`:`${String(e)} tasks`}function collapseEarlierRows(e,t){if(t.length<=50)return{rows:t};let n=t.length-49,r=t.filter(e=>e.status===`settled`).slice(0,n),i=new Set(r.map(e=>e.callId)),a=t.filter(e=>!i.has(e.callId));return r.length===0?{rows:a.slice(-50)}:{earlier:{status:r.some(t=>failedTask(e,t)||isInterrupted(e,t))?`error`:`complete`,task_id:`earlier`,title:truncate(`${countTasks(r.length)} finished earlier`,80)},rows:a.slice(-49)}}function toSlackTask(e,t){let{view:n}=e,r={status:slackStatus(n,t),task_id:slackTaskId(t.callId),title:truncate(callLabel(t),80)},i=openInteractions(n,{about:{callId:t.callId}}).at(-1);i!==void 0&&(r.details=richText(blockerLine(e,i)));let a=outputLine(e,t);return a!==void 0&&(r.output=richText(a)),r}function slackStatus(e,t){switch(callState(e,t.callId)){case`completed`:return`complete`;case`cancelled`:return isInterrupted(e,t)?`error`:`complete`;case`failed`:case`rejected`:return`error`;default:return`in_progress`}}function outputLine(e,t){let{view:n}=e,r=taskDuration(t);switch(callState(n,t.callId)){case`completed`:{let e=brief(resultText(callOutputSource(n,t.callId)?.output??t.output));if(r===void 0)return e;let i=`Done in ${r}`;return e===void 0?i:`${i}: ${e}`}case`failed`:case`rejected`:{let n=r===void 0?`Failed`:`Failed after ${r}`,i=e.audience===`private`?brief(t.error?.message):void 0;return i===void 0?n:`${n}: ${i}`}case`cancelled`:return stoppedLine(t.reason);default:return}}function stoppedLine(e){switch(e){case`task-cancel`:return`Stopped early since it was no longer needed`;case`turn-cancelled`:return`Stopped by request`;case`turn-ended`:return`Stopped when the turn ended`;default:return`Stopped`}}function blockerLine(e,t){let{request:n}=t,r=waitingText(n.kind);if(e.audience!==`private`)return r;let i=n.kind===`sign-in`?n.signIn?.displayName??n.signIn?.name??n.prompt:n.prompt,a=i===void 0?``:normalizePresentationText(i);return a===``?r:`${r}: ${a.slice(0,200)}`}function waitingText(e){switch(e){case`approval`:return`Waiting for approval`;case`sign-in`:return`Waiting for sign-in`;default:return`Waiting for a response`}}function resultText(e){if(typeof e==`string`)return e;if(typeof e!=`object`||!e||Array.isArray(e))return;let t=e;return t.message??t.text??t.summary}function brief(e){return typeof e==`string`?firstSentence(e):void 0}function richText(e){return{elements:[{elements:[{text:truncate(e,100),type:`text`}],type:`rich_text_section`}],type:`rich_text`}}function truncate(e,t){if(e.length<=t)return e;let n=e.slice(0,t-1),r=n.lastIndexOf(` `);return`${(r>t/2?n.slice(0,r):n).trimEnd()}…`}function slackTaskId(e){return e.replace(/[^a-zA-Z0-9_-]/g,`_`).slice(-200)}function slackBlockId(e){return`eve_task_card_${slackTaskId(e)}`}function withTaskCards(e,t){let n=e.committed;return{...e,async committed(e,r){try{await n?.(e,r)}finally{let{channel:n,view:i}=r,a=new Set([...e.facts.flatMap(e=>e.scope?.turnId??[]),...Object.keys(n.state.taskCards??{})]);for(let e of a)await writeTaskCards(n,sessionTables(i),e,t)}}}}async function writeTaskCards(e,t,n,r){if(t.turns[n]===void 0)return;let i=normalizeChannelAudience(e.state.audience),a=taskCards(t,n,i),o=e.state.taskCards?.[n]?.since??0;for(let t of a)t.since<o||(t.since>(e.state.taskCards?.[n]?.since??0)&&rememberCard(e.state,n,{since:t.since}),await writeChangedCard(e,n,t,r));a.at(-1)?.state===`finished`&&forgetTurn(e.state,n)}async function writeChangedCard(e,t,n,r){let{channelId:i,codeChannel:a}=e.state,o=(a?e.state.replyThreadTs:e.state.threadTs)??``,s=contextStorage.getStore()?.get(ScheduleIdKey)!==void 0;if(!i||!(o||a)||s)return;let c=renderCard(r,n);if(c===null)return;let l=e.state.taskCards?.[t]??{},u=createHash(`sha256`).update(JSON.stringify(c)).digest(`base64url`);if(u===l.fingerprint)return;let d=await writeCardWithRetry(e.slack,{card:c,channelId:i,threadTs:o,ts:l.ts});d!==void 0&&(rememberCard(e.state,t,{...l,fingerprint:u,ts:d}),d!==l.ts&&n.state===`working`&&await restoreStatus(e))}function rememberCard(e,t,n){let r={...e.taskCards,[t]:n},i=Object.keys(r);for(let e of i.slice(0,Math.max(0,i.length-20)))delete r[e];e.taskCards=r}function forgetTurn(e,t){let{[t]:n,...r}=e.taskCards??{};e.taskCards=r}function renderCard(e,t){try{return e(t)}catch(e){return logError(log,`task card renderer failed`,e,{turnId:t.turnId}),null}}async function writeCardWithRetry(e,t){for(let n of[1,2])try{return await writeCard(e,t)}catch(e){logError(log,`task card write failed`,e,{attempt:n}),n===1&&await new Promise(e=>setTimeout(e,1e3))}}async function writeCard(e,t){let n={blocks:t.card.blocks,channel:t.channelId,text:t.card.text};if(t.ts!==void 0){let r=await e.request(`chat.update`,{...n,ts:t.ts});if(r.ok===!0)return t.ts;if(r.error!==`message_not_found`)throw slackError(r.error)}let r=await e.request(`chat.postMessage`,{...n,thread_ts:slackThreadTsParam(t.threadTs),unfurl_links:!1,unfurl_media:!1});if(r.ok!==!0)throw slackError(r.error);if(typeof r.ts!=`string`||r.ts===``)throw Error(`Slack did not return a ts for the task card.`);return r.ts}function slackError(e){return Error(`Slack task card failed: ${e??`unknown_error`}`)}export{renderDefaultSlackTaskCard,withTaskCards};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SLACK_TYPING_STATUS_MAX_LENGTH,truncateTypingStatus}from"#public/channels/slack/limits.js";async function showStatus(e,t,n){let r=truncateTypingStatus(t),i=Date.now(),a=e.state.threadStatus;a?.text===r&&i-a.atMs>=0&&i-a.atMs<3e4&&n?.force!==!0||(await e.thread.startTyping(r),e.state.threadStatus={atMs:i,text:r})}async function restoreStatus(e){let t=e.state.threadStatus;t&&await showStatus(e,t.text,{force:!0})}async function hideStatus(e){let t=e.state.threadStatus;t&&(e.state.threadStatus={...t,atMs:0}),await e.thread.startTyping()}async function clearStatus(e){e.state.threadStatus=null,await e.thread.startTyping()}const HEADING=/(?:^|\n)[ \t]*\*\*([^*\n]+)\*\*/gu,SENTENCE_BOUNDARY=/(?<=[.!?])(?<!(?:^|\s)\d{1,2}\.)\s+(?=[\p{Lu}"'`(])/u,LIST_MARKER=/^(?:[-*+]|\d{1,2}[.)])\s+/u;function reasoningStatus(e,t){let n=[...e.matchAll(HEADING)].findLast(e=>e[1].trim()!==``);if(n)return n[1].trim();let r=e.split(/\r?\n/u).flatMap(e=>e.split(SENTENCE_BOUNDARY)).map(e=>e.trim().replace(LIST_MARKER,``)),i=t?.complete===!0?-1:r.length-1;for(let e=r.length-1;e>=0;--e){let t=r[e],n=e===i?SLACK_TYPING_STATUS_MAX_LENGTH:12;if(t.length>=n&&isProse(t))return t}}function isProse(e){return!e.startsWith("```")&&/\p{L}/u.test(e)}export{clearStatus,hideStatus,reasoningStatus,restoreStatus,showStatus};
|
|
1
|
+
import{setSlackCodeChannelStatus}from"#public/channels/slack/code-channel.js";import{SLACK_TYPING_STATUS_MAX_LENGTH,truncateTypingStatus}from"#public/channels/slack/limits.js";async function showStatus(e,t,n){let r=truncateTypingStatus(t),i=Date.now(),a=e.state.threadStatus;if(isCodeChannel(e)&&a!=null&&a.atMs>0){e.state.threadStatus={atMs:i,text:r};return}a?.text===r&&i-a.atMs>=0&&i-a.atMs<3e4&&n?.force!==!0||(await e.thread.startTyping(r),e.state.threadStatus={atMs:i,text:r})}async function restoreStatus(e){let t=e.state.threadStatus;t&&await showStatus(e,t.text,{force:!0})}async function hideStatus(e){let t=e.state.threadStatus;if(t&&(e.state.threadStatus={...t,atMs:0}),isCodeChannel(e)){await setSlackCodeChannelStatus(e.slack.request,e.slack.channelId,`suspended`);return}await e.thread.startTyping()}function isCodeChannel(e){return e.slack.codeChannel}async function clearStatus(e){e.state.threadStatus=null,await e.thread.startTyping()}const HEADING=/(?:^|\n)[ \t]*\*\*([^*\n]+)\*\*/gu,SENTENCE_BOUNDARY=/(?<=[.!?])(?<!(?:^|\s)\d{1,2}\.)\s+(?=[\p{Lu}"'`(])/u,LIST_MARKER=/^(?:[-*+]|\d{1,2}[.)])\s+/u;function reasoningStatus(e,t){let n=[...e.matchAll(HEADING)].findLast(e=>e[1].trim()!==``);if(n)return n[1].trim();let r=e.split(/\r?\n/u).flatMap(e=>e.split(SENTENCE_BOUNDARY)).map(e=>e.trim().replace(LIST_MARKER,``)),i=t?.complete===!0?-1:r.length-1;for(let e=r.length-1;e>=0;--e){let t=r[e],n=e===i?SLACK_TYPING_STATUS_MAX_LENGTH:12;if(t.length>=n&&isProse(t))return t}}function isProse(e){return!e.startsWith("```")&&/\p{L}/u.test(e)}export{clearStatus,hideStatus,reasoningStatus,restoreStatus,showStatus};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{isObject}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";const TEAMS_MESSAGE_TEXT_MAX_LENGTH=81920,BOT_FRAMEWORK_TENANT=`botframework.com`,accessTokenCache=new Map;function teamsContinuationToken(e){let t=normalizeTeamsContinuationAddress(e);return[e.tenantId??`_`,t.conversationId,t.replyToActivityId??``].map(e=>encodeURIComponent(e)).join(`:`)}function normalizeTeamsContinuationAddress(e){let t=e.conversationId.lastIndexOf(`;messageid=`);return t<0?{conversationId:e.conversationId,replyToActivityId:e.replyToActivityId??null}:{conversationId:e.conversationId.slice(0,t),replyToActivityId:e.conversationId.slice(t+11)||e.replyToActivityId||null}}async function resolveTeamsAppId(e){let t=e??process.env.MICROSOFT_APP_ID??process.env.TEAMS_APP_ID;if(!t)throw Error(`MICROSOFT_APP_ID or TEAMS_APP_ID is required.`);return typeof t==`function`?await t():t}async function resolveTeamsAppPassword(e){let t=e??process.env.MICROSOFT_APP_PASSWORD??process.env.TEAMS_APP_PASSWORD;if(!t)throw Error(`MICROSOFT_APP_PASSWORD or TEAMS_APP_PASSWORD is required.`);return typeof t==`function`?await t():t}async function resolveTeamsTenantId(e){let t=e??process.env.MICROSOFT_TENANT_ID??process.env.TEAMS_TENANT_ID;if(t)return typeof t==`function`?await t():t}async function resolveTeamsAccessToken(e={}){let t=e.credentials;if(t?.tokenProvider!==void 0)return normalizeAccessTokenResult(await t.tokenProvider()).accessToken;let n=await resolveTeamsAppId(t?.appId),r=await resolveTeamsAppPassword(t?.appPassword),i=await resolveTeamsTenantId(t?.tenantId),a=trimTrailingSlash(e.loginBaseUrl??`https://login.microsoftonline.com`),o=`${a}:${i??BOT_FRAMEWORK_TENANT}:${n}`,s=accessTokenCache.get(o),c=Date.now();if(s!==void 0&&s.expiresAt-6e4>c)return s.accessToken;let l=`${a}/${encodeURIComponent(i??BOT_FRAMEWORK_TENANT)}/oauth2/v2.0/token`,u=new URLSearchParams({client_id:n,client_secret:r,grant_type:`client_credentials`,scope:`https://api.botframework.com/.default`}),d=await(e.fetch??fetch)(l,{body:u,headers:{"content-type":`application/x-www-form-urlencoded`},method:`POST`}),f=await parseResponseBody(d);if(!d.ok)throw Error(`Teams access-token request failed with HTTP ${d.status}.`);let p=isObject(f)?f:{},m=typeof p.access_token==`string`?p.access_token:``;if(!m)throw Error(`Teams access-token response did not include access_token.`);let h=typeof p.expires_in==`number`?p.expires_in:3600;return accessTokenCache.set(o,{accessToken:m,expiresAt:c+h*1e3}),m}async function callTeamsConnectorApi(e){let t=e.fetch??fetch,
|
|
1
|
+
import{isObject}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";const TEAMS_MESSAGE_TEXT_MAX_LENGTH=81920,BOT_FRAMEWORK_TENANT=`botframework.com`,accessTokenCache=new Map;function teamsContinuationToken(e){let t=normalizeTeamsContinuationAddress(e);return[e.tenantId??`_`,t.conversationId,t.replyToActivityId??``].map(e=>encodeURIComponent(e)).join(`:`)}function normalizeTeamsContinuationAddress(e){let t=e.conversationId.lastIndexOf(`;messageid=`);return t<0?{conversationId:e.conversationId,replyToActivityId:e.replyToActivityId??null}:{conversationId:e.conversationId.slice(0,t),replyToActivityId:e.conversationId.slice(t+11)||e.replyToActivityId||null}}async function resolveTeamsAppId(e){let t=e??process.env.MICROSOFT_APP_ID??process.env.TEAMS_APP_ID;if(!t)throw Error(`MICROSOFT_APP_ID or TEAMS_APP_ID is required.`);return typeof t==`function`?await t():t}async function resolveTeamsAppPassword(e){let t=e??process.env.MICROSOFT_APP_PASSWORD??process.env.TEAMS_APP_PASSWORD;if(!t)throw Error(`MICROSOFT_APP_PASSWORD or TEAMS_APP_PASSWORD is required.`);return typeof t==`function`?await t():t}async function resolveTeamsTenantId(e){let t=e??process.env.MICROSOFT_TENANT_ID??process.env.TEAMS_TENANT_ID;if(t)return typeof t==`function`?await t():t}async function resolveTeamsAccessToken(e={}){let t=e.credentials;if(t?.tokenProvider!==void 0)return normalizeAccessTokenResult(await t.tokenProvider()).accessToken;let n=await resolveTeamsAppId(t?.appId),r=await resolveTeamsAppPassword(t?.appPassword),i=await resolveTeamsTenantId(t?.tenantId),a=trimTrailingSlash(e.loginBaseUrl??`https://login.microsoftonline.com`),o=`${a}:${i??BOT_FRAMEWORK_TENANT}:${n}`,s=accessTokenCache.get(o),c=Date.now();if(s!==void 0&&s.expiresAt-6e4>c)return s.accessToken;let l=`${a}/${encodeURIComponent(i??BOT_FRAMEWORK_TENANT)}/oauth2/v2.0/token`,u=new URLSearchParams({client_id:n,client_secret:r,grant_type:`client_credentials`,scope:`https://api.botframework.com/.default`}),d=await(e.fetch??fetch)(l,{body:u,headers:{"content-type":`application/x-www-form-urlencoded`},method:`POST`}),f=await parseResponseBody(d);if(!d.ok)throw Error(`Teams access-token request failed with HTTP ${d.status}.`);let p=isObject(f)?f:{},m=typeof p.access_token==`string`?p.access_token:``;if(!m)throw Error(`Teams access-token response did not include access_token.`);let h=typeof p.expires_in==`number`?p.expires_in:3600;return accessTokenCache.set(o,{accessToken:m,expiresAt:c+h*1e3}),m}async function callTeamsConnectorApi(e){let t=URL.parse(e.serviceUrl);if(t===null||t.protocol!==`https:`||t.username!==``||t.password!==``||t.search!==``||t.hash!==``)throw Error(`Teams Connector service URL must be an absolute HTTPS URL without credentials, query, or fragment.`);let n=e.fetch??fetch,r=new Headers;r.set(`authorization`,`Bearer ${await resolveTeamsAccessToken(e)}`),r.set(`content-type`,`application/json; charset=utf-8`);let i={headers:r,method:e.method??`POST`,redirect:`error`};e.body!==void 0&&(i.body=JSON.stringify(parseJsonObject(e.body)));let a=await n(`${trimTrailingSlash(e.serviceUrl)}${e.path}`,i);return{body:await parseResponseBody(a),ok:a.ok,status:a.status}}async function sendTeamsActivity(e){let t=await callTeamsConnectorApi({...e,body:e.body,path:`/v3/conversations/${encodeURIComponent(e.conversationId)}/activities`});if(!t.ok)throw Error(`Teams send activity failed with HTTP ${t.status}.`);return toPostedActivity(t.body)}async function replyToTeamsActivity(e){let t=await callTeamsConnectorApi({...e,body:e.body,path:`/v3/conversations/${encodeURIComponent(e.conversationId)}/activities/${encodeURIComponent(e.activityId)}`});if(!t.ok)throw Error(`Teams reply activity failed with HTTP ${t.status}.`);return toPostedActivity(t.body)}async function updateTeamsActivity(e){let t=await callTeamsConnectorApi({...e,body:e.body,method:`PUT`,path:`/v3/conversations/${encodeURIComponent(e.conversationId)}/activities/${encodeURIComponent(e.activityId)}`});if(!t.ok)throw Error(`Teams update activity failed with HTTP ${t.status} for conversation "${e.conversationId}" activity "${e.activityId}": ${formatConnectorErrorBody(t.body)}`);return toPostedActivity(t.body)}async function triggerTeamsTypingIndicator(e){let t=await callTeamsConnectorApi({...e,body:{type:`typing`},path:`/v3/conversations/${encodeURIComponent(e.conversationId)}/activities`});if(!t.ok)throw Error(`Teams typing indicator failed with HTTP ${t.status}.`)}function splitTeamsMessageText(e){if(e.length<=81920)return[e];let t=[],n=e;for(;n.length>TEAMS_MESSAGE_TEXT_MAX_LENGTH;){let e=n.lastIndexOf(`
|
|
2
2
|
|
|
3
3
|
`,TEAMS_MESSAGE_TEXT_MAX_LENGTH);e<=0&&(e=n.lastIndexOf(`
|
|
4
4
|
`,TEAMS_MESSAGE_TEXT_MAX_LENGTH)),e<=0&&(e=n.lastIndexOf(` `,TEAMS_MESSAGE_TEXT_MAX_LENGTH)),e<=0&&(e=TEAMS_MESSAGE_TEXT_MAX_LENGTH),t.push(n.slice(0,e).trimEnd()),n=n.slice(e).trimStart()}return t.push(n),t}function normalizeTeamsPostInput(e){return typeof e==`string`?{text:e,textFormat:`markdown`}:e}function normalizeAccessTokenResult(e){if(typeof e==`string`)return{accessToken:e};let t=e.expiresAt instanceof Date?e.expiresAt.getTime():e.expiresAt;return{accessToken:e.accessToken,expiresAt:t}}function toPostedActivity(e){let t=isObject(e)?e:{};return{id:typeof t.id==`string`?t.id:``,raw:e}}function formatConnectorErrorBody(e){let t=typeof e==`string`?e:JSON.stringify(e);return t===void 0||t.length===0?`empty response body`:t.length<=1e3?t:`${t.slice(0,997)}...`}function trimTrailingSlash(e){return e.replace(/\/+$/,``)}async function parseResponseBody(e){let t=await e.text();if(!t)return null;try{return JSON.parse(t)}catch{return t}}export{TEAMS_MESSAGE_TEXT_MAX_LENGTH,callTeamsConnectorApi,normalizeTeamsContinuationAddress,normalizeTeamsPostInput,replyToTeamsActivity,resolveTeamsAccessToken,resolveTeamsAppId,resolveTeamsAppPassword,resolveTeamsTenantId,sendTeamsActivity,splitTeamsMessageText,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{postedIn,signInEndedText}from"#channel/sign-in.js";import{displayProperName}from"#protocol/session-projection/display-name.js";import{resolvedPromptAnswer}from"#channel/resolved-prompt.js";import{surfaceEvents}from"#channel/surface.js";import{splitTeamsMessageText}from"#public/channels/teams/api.js";import{renderAnsweredInteractionMessage,renderInteractionMessage}from"#public/channels/teams/hitl.js";import{renderTeamsAuthorizationPrompt,renderTeamsAuthorizationStatus}from"#public/channels/teams/authorization.js";function defaultTeamsAuth(e){let t=e.tenantId,n={activity_id:e.id,conversation_id:e.conversation.id,scope:e.scope,user_id:e.from.id};e.from.name!==void 0&&(n.user_name=e.from.name),e.from.aadObjectId!==void 0&&(n.aad_object_id=e.from.aadObjectId),t!==void 0&&(n.tenant_id=t),e.teamId!==void 0&&(n.team_id=e.teamId),e.teamsChannelId!==void 0&&(n.channel_id=e.teamsChannelId);let r=t?`teams:${t}:${e.from.id}`:`teams:${e.from.id}`;return{attributes:n,authenticator:`teams-activity`,issuer:t?`teams:${t}`:`teams`,principalId:r,principalType:e.from.role===`bot`?`service`:`user`,subject:e.from.aadObjectId}}async function defaultOnMessage(e,t){return t.from.role===`bot`||t.from.id===t.recipient.id||t.scope!==`personal`&&!t.isBotMentioned?null:(await e.thread.startTyping(),{auth:defaultTeamsAuth(t)})}async function showTeamsRequest(e,t){let n=await e.thread.post(renderInteractionMessage(t,{adaptiveCardVersion:e.adaptiveCardVersion,replyToActivityId:e.teams.replyToActivityId}));if(!n.id)return;let r={activityId:n.id,prompt:t.request.prompt};e.state.pendingPromptCards={...e.state.pendingPromptCards,[t.interactionId]:t.request.kind===`question`?{...r,options:(t.request.options??[]).map(({id:e,label:t})=>({id:e,label:t}))}:r}}async function settleTeamsRequest(e,t){let{[t.interactionId]:n,...r}=e.state.pendingPromptCards??{};if(n===void 0)return;let{kind:i,outcome:a,responder:o}=t,s=i===`approval`&&o!==void 0&&(a===`accepted`||a===`declined`),c=resolvedPromptAnswer(t,n.options);if(s){let t=e.state.approvalResponderAccounts?.[o.id],n=t?.name??t?.id,r=a===`accepted`?`Approved`:`Cancelled`;c=n===void 0?r:`${r} by ${n}`}await e.thread.update(n.activityId,renderAnsweredInteractionMessage({includeText:!1,label:c,prompt:n.prompt})),e.state.pendingPromptCards=r}function teamsSignIns(e){return{private:e.state.conversationType===`personal`,postChallenge:async t=>(await e.thread.post(renderTeamsAuthorizationPrompt(t,signInOptions(e,t)))).id,post:async t=>(await e.thread.post({text:t})).id,postStatus:async t=>(await e.thread.post(renderTeamsAuthorizationStatus(t,signInOptions(e,t)))).id,async edit(t,n){await e.thread.update(t,renderAnsweredInteractionMessage({prompt:n}))},typing:()=>e.thread.startTyping(),posted:postedIn(e.state,`pendingAuthActivityIds`)}}function signInOptions(e,t){let n=(t.principalId===void 0?void 0:e.state.approvalResponderAccounts?.[t.principalId])??(t.responseId===void 0?e.state.triggeringUser:null);return{adaptiveCardVersion:e.adaptiveCardVersion,replyToActivityId:e.teams.replyToActivityId,requester:n==null?void 0:n.name??n.id}}const defaultEvents=surfaceEvents({typing:e=>e.thread.startTyping(),async post(e,t){for(let n of splitTeamsMessageText(t))await e.thread.post(n)},restart:`Teams conversation`,requests:{show:showTeamsRequest,settle:settleTeamsRequest},signIns:teamsSignIns});function formatConnectionDisplayName(e){return displayProperName(e)}const buildAuthCompletedText=signInEndedText;function teamsMentionUser(e){let t=e.name??e.id;return{mentioned:{id:e.id,name:e.name},text:`<at>${t}</at>`,type:`mention`}}export{buildAuthCompletedText,defaultEvents,defaultOnMessage,defaultTeamsAuth,formatConnectionDisplayName,teamsMentionUser};
|
|
1
|
+
import{postedIn,signInEndedText}from"#channel/sign-in.js";import{displayProperName}from"#protocol/session-projection/display-name.js";import{resolvedPromptAnswer}from"#channel/resolved-prompt.js";import{surfaceEvents}from"#channel/surface.js";import{splitTeamsMessageText}from"#public/channels/teams/api.js";import{renderAnsweredInteractionMessage,renderInteractionMessage}from"#public/channels/teams/hitl.js";import{renderTeamsAuthorizationPrompt,renderTeamsAuthorizationStatus}from"#public/channels/teams/authorization.js";function defaultTeamsAuth(e){let t=e.tenantId,n={activity_id:e.id,conversation_id:e.conversation.id,scope:e.scope,user_id:e.from.id};e.from.name!==void 0&&(n.user_name=e.from.name),e.from.aadObjectId!==void 0&&(n.aad_object_id=e.from.aadObjectId),t!==void 0&&(n.tenant_id=t),e.teamId!==void 0&&(n.team_id=e.teamId),e.teamsChannelId!==void 0&&(n.channel_id=e.teamsChannelId);let r=t?`teams:${t}:${e.from.id}`:`teams:${e.from.id}`;return{attributes:n,authenticator:`teams-activity`,issuer:t?`teams:${t}`:`teams`,principalId:r,principalType:e.from.role===`bot`?`service`:`user`,subject:e.from.aadObjectId}}async function defaultOnMessage(e,t){return t.from.role===`bot`||t.from.id===t.recipient.id||t.scope!==`personal`&&!t.isBotMentioned?null:(await e.thread.startTyping(),{auth:defaultTeamsAuth(t)})}async function showTeamsRequest(e,t){let n=await e.thread.post(renderInteractionMessage(t,{adaptiveCardVersion:e.adaptiveCardVersion,replyToActivityId:e.teams.replyToActivityId}));if(!n.id)return;let r={activityId:n.id,prompt:t.request.prompt};e.state.pendingPromptCards={...e.state.pendingPromptCards,[t.interactionId]:t.request.kind===`question`?{...r,options:(t.request.options??[]).map(({id:e,label:t})=>({id:e,label:t}))}:r}}async function settleTeamsRequest(e,t){let{[t.interactionId]:n,...r}=e.state.pendingPromptCards??{};if(n===void 0)return;let{kind:i,outcome:a,responder:o}=t,s=i===`approval`&&o!==void 0&&(a===`accepted`||a===`declined`),c=resolvedPromptAnswer(t,n.options);if(s){let t=e.state.approvalResponderAccounts?.[o.id],n=t?.name??t?.id,r=a===`accepted`?`Approved`:`Cancelled`;c=n===void 0?r:`${r} by ${n}`}await e.thread.update(n.activityId,renderAnsweredInteractionMessage({includeText:!1,label:c,prompt:n.prompt})),e.state.pendingPromptCards=r}function teamsSignIns(e){return{private:e.state.conversationType===`personal`,postChallenge:async t=>(await e.thread.post(renderTeamsAuthorizationPrompt(t,signInOptions(e,t)))).id,post:async t=>(await e.thread.post({text:t})).id,postStatus:async t=>(await e.thread.post(renderTeamsAuthorizationStatus(t,signInOptions(e,t)))).id,async edit(t,n){await e.thread.update(t,renderAnsweredInteractionMessage({prompt:n}))},typing:()=>e.thread.startTyping(),posted:postedIn(e.state,`pendingAuthActivityIds`)}}function signInOptions(e,t){let n=(t.principalId===void 0?void 0:e.state.approvalResponderAccounts?.[t.principalId])??(t.responseId===void 0?e.state.triggeringUser:null);return{adaptiveCardVersion:e.adaptiveCardVersion,replyToActivityId:e.teams.replyToActivityId,requester:n==null?void 0:n.name??n.id}}const defaultEvents=surfaceEvents({typing:e=>e.thread.startTyping(),async post(e,t){for(let n of splitTeamsMessageText(t))await e.thread.post(n)},restart:`Teams conversation`,requests:{show:showTeamsRequest,settle:settleTeamsRequest},signIns:teamsSignIns});function formatConnectionDisplayName(e){return displayProperName(e)}const buildAuthCompletedText=signInEndedText;function teamsMentionUser(e){let t=(e.name??e.id).replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`);return{mentioned:{id:e.id,name:e.name},text:`<at>${t}</at>`,type:`mention`}}export{buildAuthCompletedText,defaultEvents,defaultOnMessage,defaultTeamsAuth,formatConnectionDisplayName,teamsMentionUser};
|
|
@@ -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{INACTIVE_REQUEST_NOTICE,respondOrRetireInactivePrompt}from"#channel/inactive-prompt.js";import{callTeamsConnectorApi,normalizeTeamsContinuationAddress,normalizeTeamsPostInput,replyToTeamsActivity,sendTeamsActivity,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{deriveTeamsAnswers,isTeamsAnswerActivity,isTeamsToolApprovalResponseActivity,readTeamsInputPrompt,readTeamsInputReplyToActivityId,readTeamsToolApprovalPrompt,renderInactiveInputRequestMessage,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{answerTeamsAuthorizationReveal,readTeamsAuthorizationReveal}from"#public/channels/teams/authorization.js";import{defaultEvents,defaultOnMessage,defaultTeamsAuth,teamsMentionUser}from"#public/channels/teams/defaults.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{formatTeamsContextBlock,parseTeamsActivity,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),n=e.onMessage??defaultOnMessage,r=e.onAnswers??(e.onMessage===void 0?defaultOnAnswers:rejectInput),i={...defaultEvents,...e.events};return defineChannel({turnPolicy:e.turnPolicy,uploadPolicy:e.files?.uploadPolicy,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t,{...e.api,credentials:e.credentials}),metadata:e=>({channelId:e.channelId,conversationType:e.conversationType,teamId:e.teamId}),audience:({state:e})=>teamsAudience(e.conversationType),context(t,n){return rebuildTeamsContext(t,n,e)},deliver(e,t){let n=e.state,r=n?.pendingPromptCards;r!==void 0&&(t.state.pendingPromptCards={...r,...t.state.pendingPromptCards});let i=n?.approvalResponderAccounts;i!==void 0&&(t.state.approvalResponderAccounts={...t.state.approvalResponderAccounts,...i})},routes:[POST(e.route??`/eve/v1/teams`,async(i,{from:a,resolveSession:o,waitUntil:s})=>{let c=await verifyInbound(i,e.credentials);if(c===null)return new Response(`unauthorized`,{status:401});let l;try{l=parseJsonObject(JSON.parse(c))}catch(e){return log.warn(`inbound Teams body is not valid JSON`,{error:e}),teamsOk()}let u=parseTeamsActivity(l);return u===null?teamsOk():u.type===`message`?(s(isTeamsAnswerActivity(u)?dispatchAnswers({activity:u,config:e,from:a,onAnswers:r}).then(e=>e?.outcome===`inactive`?e.retirement:void 0):dispatchMessage({activity:u,config:e,filesPolicy:t,onMessage:n,from:a,resolveSession:o})),teamsOk()):u.type===`invoke`?handleInvoke({activity:u,config:e,onAnswers:r,resolveSession:o,waitUntil:s,from:a}):teamsOk()})],async receive(t,{from:n}){let r=t.target,i=readNonEmptyString(r.serviceUrl),a=readNonEmptyString(r.conversationId);if(!i||!a)throw Error(`teamsChannel().receive requires target.serviceUrl and target.conversationId.`);let o=readNonEmptyString(r.conversationType)??null,s=readNonEmptyString(r.replyToActivityId)??null,c=r.initialMessage;if(c!==void 0&&s!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let l={...initialTeamsState(),channelId:readNonEmptyString(r.channelId)??null,conversationId:a,conversationType:o,replyToActivityId:s,serviceUrl:i,teamId:readNonEmptyString(r.teamId)??null,tenantId:readNonEmptyString(r.tenantId)??null};if(c!==void 0){let t=await buildTeamsBinding({config:e,state:l}).thread.post(c);o!==`personal`&&t.id&&(s=t.id,l.replyToActivityId=t.id)}return n(teamsContinuationToken({conversationId:a,replyToActivityId:s,tenantId:l.tenantId})).send(t.message,{auth:t.auth,state:l})},events:i})}function teamsAudience(e){return e===`personal`||e===`groupChat`?`private`:`unknown`}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,state:e}}function buildTeamsBinding(e){let t=buildTeamsHandle(e);return{teams:t,thread:{mentionUser:teamsMentionUser,post(e){return t.sendActivity(e)},async startTyping(){try{await t.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,n){return t.updateActivity(e,n)}}}}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?.alias(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:buildUpdateActivity(t,i),credentials:r,activityId:e,conversationId:normalizeTeamsContinuationAddress({conversationId:a.conversationId}).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,botId:e.activity.recipient.id,channelId:e.activity.teamsChannelId,conversationId:e.activity.conversation.id,conversationType:e.activity.conversationType,isMentioned:e.activity.isBotMentioned,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],history:a.history,state:t,title:a.title})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){let t=readTeamsAuthorizationReveal(e.activity);if(t!==null)return Response.json(await revealAuthorization({...e,reveal:t}));if(isTeamsAnswerActivity(e.activity)){let t=await dispatchAnswers({activity:e.activity,config:e.config,onAnswers:e.onAnswers,from:e.from});return t?.outcome===`inactive`?(e.waitUntil(t.retirement),Response.json(teamsInvokeResponse({message:INACTIVE_REQUEST_NOTICE}))):Response.json(teamsInvokeResponse())}if(e.config.onInvoke===void 0)return teamsOk();let n=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),r=await e.config.onInvoke(n,e.activity);return r instanceof Response?r:r&&typeof r==`object`?Response.json(r):teamsOk()}async function revealAuthorization(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r=null;try{r=(await e.onAnswers(n,e.activity))?.auth??null}catch(e){log.error(`Teams sign-in press authorization failed`,{error:e})}let i=cardContinuationToken(e.activity,t,e.reveal.replyToActivityId??null);return await answerTeamsAuthorizationReveal({adaptiveCardVersion:e.config.adaptiveCardVersion??`1.5`,auth:r,presser:e.activity.from,resolveSession:()=>e.resolveSession(i),reveal:e.reveal})}async function dispatchAnswers(e){let t=deriveTeamsAnswers(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity),r=buildTeamsBinding({config:e.config,state:n}),i;try{i=await e.onAnswers(r,e.activity)}catch(e){log.error(`Teams input response authorization failed`,{error:e});return}if(i===null)return;let a=i.auth,o=e.activity;return respondOrRetireInactivePrompt(()=>e.from(cardContinuationToken(o,n,readTeamsInputReplyToActivityId(o))).respond(t,{auth:a,state:approvalResponseStatePatch(o,a)}),{async retirePrompt(e){o.replyToId!==void 0&&await r.thread.update(o.replyToId,renderInactiveInputRequestMessage(readTeamsInputPrompt(o),e))}},log)}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,pendingAuthActivityIds:{},replyToActivityId:t.replyToActivityId,serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function cardContinuationToken(e,t,n){return n===null?stateToken(t):teamsContinuationToken({conversationId:e.conversation.id,replyToActivityId:n,tenantId:e.tenantId})}function approvalResponseStatePatch(e,t){if(t?.principalId===void 0||!isTeamsToolApprovalResponseActivity(e))return;let n=deriveTeamsAnswers(e)[0]?.interactionId,r=e.replyToId;return n===void 0||r===void 0?{approvalResponderAccounts:{[t.principalId]:e.from}}:{approvalResponderAccounts:{[t.principalId]:e.from},pendingPromptCards:{[n]:{activityId:r,prompt:readTeamsToolApprovalPrompt(e)??`Tool approval`}}}}function defaultOnAnswers(e,t){return{auth:defaultTeamsAuth(t)}}function rejectInput(){return null}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,approvalResponderAccounts:{},pendingPromptCards:{},pendingAuthActivityIds:{},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 buildUpdateActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)throw Error(`teamsChannel: cannot replace an activity with a typing indicator.`);let{channelData:n,conversation:r,from:i,recipient:a,replyToId:o,type:s,...c}=normalizeTeamsPostInput(t);return{...c,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{INACTIVE_REQUEST_NOTICE,respondOrRetireInactivePrompt}from"#channel/inactive-prompt.js";import{callTeamsConnectorApi,normalizeTeamsContinuationAddress,normalizeTeamsPostInput,replyToTeamsActivity,sendTeamsActivity,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{deriveTeamsAnswers,isTeamsAnswerActivity,isTeamsToolApprovalResponseActivity,readTeamsInputPrompt,readTeamsInputReplyToActivityId,readTeamsToolApprovalPrompt,renderInactiveInputRequestMessage,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{answerTeamsAuthorizationReveal,readTeamsAuthorizationReveal}from"#public/channels/teams/authorization.js";import{defaultEvents,defaultOnMessage,defaultTeamsAuth,teamsMentionUser}from"#public/channels/teams/defaults.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{formatTeamsContextBlock,parseTeamsActivity,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),n=e.onMessage??defaultOnMessage,r=e.onAnswers??(e.onMessage===void 0?defaultOnAnswers:rejectInput),i={...defaultEvents,...e.events};return defineChannel({turnPolicy:e.turnPolicy,uploadPolicy:e.files?.uploadPolicy,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t,{...e.api,credentials:e.credentials}),metadata:e=>({channelId:e.channelId,conversationType:e.conversationType,teamId:e.teamId}),audience:({state:e})=>teamsAudience(e.conversationType),context(t,n){return rebuildTeamsContext(t,n,e)},deliver(e,t){let n=e.state,r=n?.pendingPromptCards;r!==void 0&&(t.state.pendingPromptCards={...r,...t.state.pendingPromptCards});let i=n?.approvalResponderAccounts;i!==void 0&&(t.state.approvalResponderAccounts={...t.state.approvalResponderAccounts,...i})},routes:[POST(e.route??`/eve/v1/teams`,async(i,{from:a,resolveSession:o,waitUntil:s})=>{let c=await verifyInbound(i,e.credentials);if(c===null)return new Response(`unauthorized`,{status:401});let l;try{l=parseJsonObject(JSON.parse(c.body))}catch(e){return log.warn(`inbound Teams body is not valid JSON`,{error:e}),teamsOk()}if(c.serviceUrl!==void 0&&l.serviceUrl!==c.serviceUrl)return log.warn(`Teams activity service URL does not match the verified token.`),new Response(`unauthorized`,{status:401});let u=parseTeamsActivity(l);return u===null?teamsOk():u.type===`message`?(s(isTeamsAnswerActivity(u)?dispatchAnswers({activity:u,config:e,from:a,onAnswers:r}).then(e=>e?.outcome===`inactive`?e.retirement:void 0):dispatchMessage({activity:u,config:e,filesPolicy:t,onMessage:n,from:a,resolveSession:o})),teamsOk()):u.type===`invoke`?handleInvoke({activity:u,config:e,onAnswers:r,resolveSession:o,waitUntil:s,from:a}):teamsOk()})],async receive({message:t,target:n,...r},{from:i}){let a=n,o=readNonEmptyString(a.serviceUrl),s=readNonEmptyString(a.conversationId);if(!o||!s)throw Error(`teamsChannel().receive requires target.serviceUrl and target.conversationId.`);let c=readNonEmptyString(a.conversationType)??null,l=readNonEmptyString(a.replyToActivityId)??null,u=a.initialMessage;if(u!==void 0&&l!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let d={...initialTeamsState(),channelId:readNonEmptyString(a.channelId)??null,conversationId:s,conversationType:c,replyToActivityId:l,serviceUrl:o,teamId:readNonEmptyString(a.teamId)??null,tenantId:readNonEmptyString(a.tenantId)??null};if(u!==void 0){let t=await buildTeamsBinding({config:e,state:d}).thread.post(u);c!==`personal`&&t.id&&(l=t.id,d.replyToActivityId=t.id)}return i(teamsContinuationToken({conversationId:s,replyToActivityId:l,tenantId:d.tenantId})).send(t,{...r,state:d})},events:i})}function teamsAudience(e){return e===`personal`||e===`groupChat`?`private`:`unknown`}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,state:e}}function buildTeamsBinding(e){let t=buildTeamsHandle(e);return{teams:t,thread:{mentionUser:teamsMentionUser,post(e){return t.sendActivity(e)},async startTyping(){try{await t.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,n){return t.updateActivity(e,n)}}}}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?.alias(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:buildUpdateActivity(t,i),credentials:r,activityId:e,conversationId:normalizeTeamsContinuationAddress({conversationId:a.conversationId}).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,botId:e.activity.recipient.id,channelId:e.activity.teamsChannelId,conversationId:e.activity.conversation.id,conversationType:e.activity.conversationType,isMentioned:e.activity.isBotMentioned,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],history:a.history,state:t,title:a.title})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){let t=readTeamsAuthorizationReveal(e.activity);if(t!==null)return Response.json(await revealAuthorization({...e,reveal:t}));if(isTeamsAnswerActivity(e.activity)){let t=await dispatchAnswers({activity:e.activity,config:e.config,onAnswers:e.onAnswers,from:e.from});return t?.outcome===`inactive`?(e.waitUntil(t.retirement),Response.json(teamsInvokeResponse({message:INACTIVE_REQUEST_NOTICE}))):Response.json(teamsInvokeResponse())}if(e.config.onInvoke===void 0)return teamsOk();let n=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),r=await e.config.onInvoke(n,e.activity);return r instanceof Response?r:r&&typeof r==`object`?Response.json(r):teamsOk()}async function revealAuthorization(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r=null;try{r=(await e.onAnswers(n,e.activity))?.auth??null}catch(e){log.error(`Teams sign-in press authorization failed`,{error:e})}let i=cardContinuationToken(e.activity,t,e.reveal.replyToActivityId??null);return await answerTeamsAuthorizationReveal({adaptiveCardVersion:e.config.adaptiveCardVersion??`1.5`,auth:r,presser:e.activity.from,resolveSession:()=>e.resolveSession(i),reveal:e.reveal})}async function dispatchAnswers(e){let t=deriveTeamsAnswers(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity),r=buildTeamsBinding({config:e.config,state:n}),i;try{i=await e.onAnswers(r,e.activity)}catch(e){log.error(`Teams input response authorization failed`,{error:e});return}if(i===null)return;let a=i.auth,o=e.activity;return respondOrRetireInactivePrompt(()=>e.from(cardContinuationToken(o,n,readTeamsInputReplyToActivityId(o))).respond(t,{auth:a,state:approvalResponseStatePatch(o,a)}),{async retirePrompt(e){o.replyToId!==void 0&&await r.thread.update(o.replyToId,renderInactiveInputRequestMessage(readTeamsInputPrompt(o),e))}},log)}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,pendingAuthActivityIds:{},replyToActivityId:t.replyToActivityId,serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function cardContinuationToken(e,t,n){return n===null?stateToken(t):teamsContinuationToken({conversationId:e.conversation.id,replyToActivityId:n,tenantId:e.tenantId})}function approvalResponseStatePatch(e,t){if(t?.principalId===void 0||!isTeamsToolApprovalResponseActivity(e))return;let n=deriveTeamsAnswers(e)[0]?.interactionId,r=e.replyToId;return n===void 0||r===void 0?{approvalResponderAccounts:{[t.principalId]:e.from}}:{approvalResponderAccounts:{[t.principalId]:e.from},pendingPromptCards:{[n]:{activityId:r,prompt:readTeamsToolApprovalPrompt(e)??`Tool approval`}}}}function defaultOnAnswers(e,t){return{auth:defaultTeamsAuth(t)}}function rejectInput(){return null}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,approvalResponderAccounts:{},pendingPromptCards:{},pendingAuthActivityIds:{},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 buildUpdateActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)throw Error(`teamsChannel: cannot replace an activity with a typing indicator.`);let{channelData:n,conversation:r,from:i,recipient:a,replyToId:o,type:s,...c}=normalizeTeamsPostInput(t);return{...c,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};
|
|
@@ -36,15 +36,19 @@ export interface TeamsJwtVerifyOptions {
|
|
|
36
36
|
readonly maxSkewSeconds?: number;
|
|
37
37
|
readonly openIdMetadataUrl?: string;
|
|
38
38
|
}
|
|
39
|
+
export interface VerifiedTeamsRequest {
|
|
40
|
+
readonly body: string;
|
|
41
|
+
/** Native Connector claim; a trusted custom verifier owns its own binding. */
|
|
42
|
+
readonly serviceUrl?: string;
|
|
43
|
+
}
|
|
39
44
|
/**
|
|
40
|
-
* Verifies an inbound Teams request and returns
|
|
45
|
+
* Verifies an inbound Teams request and returns its body and native service URL binding.
|
|
41
46
|
*
|
|
42
47
|
* Uses `webhookVerifier` when provided, otherwise validates the bearer JWT
|
|
43
48
|
* against Bot Connector keys and the resolved app id. Throws when the
|
|
44
|
-
* verifier rejects the request, the bearer token
|
|
45
|
-
* resolved
|
|
46
|
-
* keys cannot be loaded, or JWT verification fails.
|
|
49
|
+
* verifier rejects the request, the bearer token or native service URL claim
|
|
50
|
+
* is missing, no app id can be resolved, keys cannot be loaded, or JWT verification fails.
|
|
47
51
|
*/
|
|
48
|
-
export declare function verifyTeamsRequest(request: Request, options: TeamsVerifyOptions): Promise<
|
|
52
|
+
export declare function verifyTeamsRequest(request: Request, options: TeamsVerifyOptions): Promise<VerifiedTeamsRequest>;
|
|
49
53
|
/** Verifies one Bot Connector JWT and returns its payload. */
|
|
50
54
|
export declare function verifyTeamsJwt(token: string, options: TeamsJwtVerifyOptions): Promise<JWTPayload>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{isObject}from"#shared/guards.js";import{importJWK,jwtVerify}from"#compiled/jose/index.js";import{resolveTeamsAppId}from"#public/channels/teams/api.js";const log=createLogger(`teams.verify`);async function verifyTeamsRequest(e,t){let n=await e.text();if(t.webhookVerifier!==void 0){let r=await t.webhookVerifier(e,n);if(!r)throw Error(`teamsChannel: inbound webhook verifier rejected the request.`);return
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{isObject}from"#shared/guards.js";import{importJWK,jwtVerify}from"#compiled/jose/index.js";import{resolveTeamsAppId}from"#public/channels/teams/api.js";const log=createLogger(`teams.verify`);async function verifyTeamsRequest(e,t){let n=await e.text();if(t.webhookVerifier!==void 0){let r=await t.webhookVerifier(e,n);if(!r)throw Error(`teamsChannel: inbound webhook verifier rejected the request.`);return{body:typeof r==`string`?r:n}}let r=readBearerToken(e.headers.get(`authorization`)??``);if(!r)throw Error(`teamsChannel: inbound request missing bearer token.`);let i=await verifyTeamsJwt(r,t);if(typeof i.serviceurl!=`string`)throw Error(`teamsChannel: verified token is missing its service URL claim.`);return{body:n,serviceUrl:i.serviceurl}}async function verifyTeamsJwt(e,t){let n=await resolveTeamsAppId(t.appId),r=await loadJwks(t);return(await jwtVerify(e,async e=>{let t=selectJwk(r,e),n=typeof e.alg==`string`?e.alg:void 0;return importJWK(t,n)},{audience:n,clockTolerance:t.maxSkewSeconds??300,issuer:`https://api.botframework.com`})).payload}function readBearerToken(e){let[t,n]=e.split(/\s+/,2);return t?.toLowerCase()!==`bearer`||!n?null:n}async function loadJwks(e){let t=e.fetch??fetch,n=await t(e.jwksUrl??await loadJwksUrl(e,t),{headers:{accept:`application/json`}});if(!n.ok)throw Error(`Teams JWKS route returned HTTP ${n.status}.`);let r=await n.json();if(!isObject(r)||!Array.isArray(r.keys))throw Error(`Teams JWKS response is malformed.`);return r.keys.filter(isObject)}async function loadJwksUrl(e,t){let n=await t(e.openIdMetadataUrl??`https://login.botframework.com/v1/.well-known/openidconfiguration`,{headers:{accept:`application/json`}});if(!n.ok)throw Error(`Teams OpenID metadata route returned HTTP ${n.status}.`);let r=await n.json();if(!isObject(r)||typeof r.jwks_uri!=`string`)throw Error(`Teams OpenID metadata response is missing jwks_uri.`);return r.jwks_uri}function selectJwk(e,t){let n=typeof t.kid==`string`?t.kid:void 0,r=typeof t.x5t==`string`?t.x5t:void 0,i=e.find(e=>n!==void 0&&e.kid===n||r!==void 0&&e.x5t===r)??(e.length===1?e[0]:void 0);if(i===void 0)throw log.debug(`Teams JWT key not found`,{kid:n,x5t:r}),Error(`teamsChannel: inbound JWT key was not found in JWKS.`);return i}export{verifyTeamsJwt,verifyTeamsRequest};
|
|
@@ -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{INACTIVE_REQUEST_NOTICE,respondOrRetireInactivePrompt}from"#channel/inactive-prompt.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,editTelegramMessageText,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_AUTHORIZATION_CALLBACK_PREFIX}from"#public/channels/telegram/authorization.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramAnswers,telegramCallbackAnswer,telegramReplyAnswers,telegramResolvedPromptText}from"#public/channels/telegram/hitl.js";import{defaultEvents,defaultOnMessage,defaultTelegramAuth,isTelegramBotMentioned}from"#public/channels/telegram/defaults.js";import{dispatchTelegramAuthorizationCallback}from"#public/channels/telegram/authorization-callback.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{initialTelegramState,stateFromTelegramCallbackQuery,stateFromTelegramMessage,telegramContinuationTokenFromState}from"#public/channels/telegram/state.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";import{telegramInstrumentation}from"#public/channels/telegram/audience.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},i=defineChannel({turnPolicy:e.turnPolicy,uploadPolicy:e.uploadPolicy,state:initialTelegramState(e.botUsername),...telegramInstrumentation,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:i,resolveSession: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 l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:n,uploadPolicy:t,from:i})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,from:i,resolveSession:a})),new Response(`ok`))})],async receive(t,{from:
|
|
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{INACTIVE_REQUEST_NOTICE,respondOrRetireInactivePrompt}from"#channel/inactive-prompt.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,editTelegramMessageText,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_AUTHORIZATION_CALLBACK_PREFIX}from"#public/channels/telegram/authorization.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramAnswers,telegramCallbackAnswer,telegramReplyAnswers,telegramResolvedPromptText}from"#public/channels/telegram/hitl.js";import{defaultEvents,defaultOnMessage,defaultTelegramAuth,isTelegramBotMentioned}from"#public/channels/telegram/defaults.js";import{dispatchTelegramAuthorizationCallback}from"#public/channels/telegram/authorization-callback.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{initialTelegramState,stateFromTelegramCallbackQuery,stateFromTelegramMessage,telegramContinuationTokenFromState}from"#public/channels/telegram/state.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";import{telegramInstrumentation}from"#public/channels/telegram/audience.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},i=defineChannel({turnPolicy:e.turnPolicy,uploadPolicy:e.uploadPolicy,state:initialTelegramState(e.botUsername),...telegramInstrumentation,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:i,resolveSession: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 l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:n,uploadPolicy:t,from:i})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,from:i,resolveSession:a})),new Response(`ok`))})],async receive({message:t,target:n,...r},{from:i}){let a=n,o=readChatId(a.chatId);if(o===void 0)throw Error(`telegramChannel().receive requires target.chatId.`);let s=typeof a.messageThreadId==`number`?a.messageThreadId:void 0,c=readChatId(a.conversationId),l=a.initialMessage;if(l!==void 0&&c!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let u={...initialTelegramState(e.botUsername),chatId:o,conversationId:c??null,messageThreadId:s??null};return l!==void 0&&await buildTelegramHandle({config:e,state:u}).sendMessage(l),i(telegramContinuationTokenFromState(u)).send(t,{...r,state:u})},events:r});return attachTelegramDeliver(i),i}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let t=e.config.api,n=e.state,r=e.config.credentials;function anchor(t){let r=n.chatType??t.chatType??null;n.chatType===null&&t.chatType!==void 0&&(n.chatType=t.chatType),t.id&&shouldAnchorTelegramConversation(r)&&(n.conversationId=t.id,n.chatId&&e.session?.continuation?.alias(telegramContinuationToken({chatId:n.chatId,conversationId:t.id,messageThreadId:n.messageThreadId??void 0})))}async function sendOne(e){let t=await sendMessage(e);return anchor(t),t}async function sendMessage(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for outbound message.`);return sendTelegramMessage({apiBaseUrl:t?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??n.messageThreadId??void 0},credentials:r,fetch:t?.fetch,fileBaseUrl:t?.fileBaseUrl,chatId:i})}return{botUsername:n.botUsername??e.config.botUsername,chatId:n.chatId??``,chatType:n.chatType??void 0,conversationId:n.conversationId??void 0,messageThreadId:n.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:t?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:r,fetch:t?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},editMessageText(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for text edit.`);return editTelegramMessageText({apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup,text:e.text})},post(e){return postTelegramMessage(e,sendOne)},postEphemeral(e,t,n){return postTelegramMessage({...typeof t==`string`?{text:t}:t,ephemeral_message_parameters:{...n?.callbackQueryId===void 0?{}:{callback_query_id:n.callbackQueryId},receiver_user_id:e}},sendMessage)},request(e,n){return callTelegramApi({apiBaseUrl:t?.apiBaseUrl,body:n,botToken:r?.botToken,fetch:t?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let i=n.chatId??``;if(i)try{await sendTelegramChatAction({action:e,apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageThreadId:n.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:i})}}}}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}:{...n.ephemeral_message_parameters===void 0?{}:{ephemeral_message_parameters:n.ephemeral_message_parameters},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=stateFromTelegramMessage(e.message,e.config.botUsername),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,isMentioned:isTelegramBotMentioned(e.message,e.config.botUsername),messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),s=r.context??[];try{await e.from(telegramContinuationTokenFromState(t)).send(a,{auth:r.auth,context:[o,...s],history:r.history,state:t,title:r.title})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromTelegramCallbackQuery(e.query,e.config.botUsername),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_AUTHORIZATION_CALLBACK_PREFIX)===!0){await dispatchTelegramAuthorizationCallback({continuationToken:telegramContinuationTokenFromState(t),query:e.query,resolveSession:e.resolveSession,state:t,telegram:n});return}if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){let r=e.query.message,i=e.query.data,a=r&&t.chatId?await respondOrRetireInactivePrompt(()=>e.from(telegramContinuationTokenFromState(t)).respond([telegramCallbackAnswer(i)],{auth:defaultTelegramAuth({...r,from:e.query.from})}),{retirePrompt:e=>retireInactivePrompt(n,r,e)},log):void 0;try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:a?.outcome===`inactive`?INACTIVE_REQUEST_NOTICE:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}a?.outcome===`inactive`&&await a.retirement;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})}}async function retireInactivePrompt(e,t,n){let r={inline_keyboard:[]};if(t.text===void 0){await e.telegram.editMessageReplyMarkup({messageId:t.messageId,replyMarkup:r});return}await e.telegram.editMessageText({messageId:t.messageId,replyMarkup:r,text:telegramResolvedPromptText(t.text,n)})}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.state,r=[...e.answers??[],...telegramReplyAnswers(n?.replyToBotMessageId,e.message)];if(r.some(isTelegramSyntheticResponse)){let n=resolveTelegramAnswers(t.state,r);return n.length>0?{answers:n,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
|