eve 0.81.1 → 0.82.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 +24 -0
- package/dist/src/channel/channel-address.d.ts +21 -2
- package/dist/src/channel/channel-address.js +1 -1
- package/dist/src/channel/channel-operations.d.ts +32 -1
- package/dist/src/channel/channel-operations.js +1 -1
- package/dist/src/channel/delivery-idempotency.d.ts +4 -0
- package/dist/src/channel/delivery-idempotency.js +1 -0
- package/dist/src/channel/routes.d.ts +6 -0
- package/dist/src/channel/session.d.ts +5 -0
- package/dist/src/channel/session.js +1 -1
- package/dist/src/channel/types.d.ts +14 -11
- package/dist/src/chunks/{use-eve-agent-Bp6nbhfV.js → use-eve-agent-DTu78IAF.js} +36 -0
- package/dist/src/chunks/{use-eve-agent-DLkjJIm3.js → use-eve-agent-DlznYwQk.js} +36 -0
- package/dist/src/cli/dev/command-options.d.ts +1 -0
- package/dist/src/cli/dev/diagnostic-dump.js +1 -1
- package/dist/src/cli/run.d.ts +1 -0
- package/dist/src/cli/run.js +1 -1
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/client/message-reducer-types.d.ts +7 -3
- package/dist/src/client/message-reducer.js +1 -1
- package/dist/src/client/transcript-reducer.d.ts +6 -3
- package/dist/src/client/transcript-reducer.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +1 -1
- package/dist/src/compiler/extension-compatibility.d.ts +19 -18
- package/dist/src/compiler/extension-compatibility.js +1 -1
- package/dist/src/compiler/load-binding-namespace.d.ts +2 -0
- package/dist/src/compiler/load-binding-namespace.js +1 -1
- package/dist/src/compiler/manifest.js +1 -1
- package/dist/src/compiler/module-lifecycle.d.ts +1 -1
- package/dist/src/compiler/module-lifecycle.js +1 -1
- package/dist/src/compiler/module-map.js +1 -1
- package/dist/src/compiler/normalize-agent-config.js +1 -1
- package/dist/src/compiler/normalize-manifest.js +1 -1
- package/dist/src/compiler/project-sources.d.ts +6 -0
- package/dist/src/compiler/project-sources.js +1 -1
- package/dist/src/context/keys.d.ts +2 -0
- package/dist/src/context/keys.js +1 -1
- package/dist/src/evals/cli/eval-state-directory.d.ts +7 -0
- package/dist/src/evals/cli/eval-state-directory.js +1 -0
- package/dist/src/evals/cli/eval.js +2 -2
- package/dist/src/eve-channel/index.js +1 -1
- package/dist/src/eve-channel/support.d.ts +2 -0
- package/dist/src/eve-channel/support.js +1 -1
- package/dist/src/eve-channel/types.d.ts +7 -0
- package/dist/src/execution/agent-sessions/context.d.ts +2 -13
- package/dist/src/execution/agent-sessions/context.js +1 -1
- package/dist/src/execution/agent-sessions/runtime-overrides.d.ts +5 -0
- package/dist/src/execution/agent-sessions/runtime-overrides.js +1 -0
- package/dist/src/execution/agent-sessions/session.d.ts +2 -2
- package/dist/src/execution/agent-sessions/session.js +1 -1
- package/dist/src/execution/agent-sessions/steps.d.ts +2 -0
- package/dist/src/execution/agent-sessions/steps.js +1 -1
- package/dist/src/execution/coordination-dispatch-shared.d.ts +5 -5
- package/dist/src/execution/coordination-dispatch-step.d.ts +17 -2
- package/dist/src/execution/coordination-dispatch-step.js +1 -1
- package/dist/src/execution/create-session-step.d.ts +5 -2
- package/dist/src/execution/create-session-step.js +1 -1
- package/dist/src/execution/deliver-payloads.js +1 -1
- package/dist/src/execution/durable-session-store.d.ts +0 -1
- package/dist/src/execution/dynamic-connection-recovery.js +1 -1
- package/dist/src/execution/dynamic-workflow/tool.js +1 -1
- package/dist/src/execution/effective-agent-config.d.ts +8 -2
- package/dist/src/execution/effective-agent-config.js +1 -1
- package/dist/src/execution/install-workflow-deployment-id.d.ts +3 -0
- package/dist/src/execution/install-workflow-deployment-id.js +1 -0
- package/dist/src/execution/legacy-session/turn-workflow.js +1 -1
- package/dist/src/execution/node-step.d.ts +1 -0
- package/dist/src/execution/node-step.js +1 -1
- package/dist/src/execution/runtime-context.d.ts +2 -0
- package/dist/src/execution/runtime-context.js +1 -1
- package/dist/src/execution/session/active-turn.js +1 -1
- package/dist/src/execution/session/admission.js +1 -1
- package/dist/src/execution/session/checkpoint-migrations.js +1 -1
- package/dist/src/execution/session/entry-input.d.ts +1 -3
- package/dist/src/execution/session/entry.js +1 -1
- package/dist/src/execution/session/finalization.js +1 -1
- package/dist/src/execution/session/handoff.d.ts +6 -9
- package/dist/src/execution/session/handoff.js +1 -1
- package/dist/src/execution/session/input-queue.d.ts +5 -1
- package/dist/src/execution/session/input-queue.js +1 -1
- package/dist/src/execution/session/pending-history.d.ts +5 -0
- package/dist/src/execution/session/pending-history.js +1 -0
- package/dist/src/execution/session/pending-turn-state.js +1 -1
- package/dist/src/execution/session/program.d.ts +1 -1
- package/dist/src/execution/session/program.js +1 -1
- package/dist/src/execution/session/timeout-control.d.ts +18 -4
- package/dist/src/execution/session/timeout-control.js +1 -1
- package/dist/src/execution/session/timeout.d.ts +1 -2
- package/dist/src/execution/session/timeout.js +1 -1
- package/dist/src/execution/session/turn-event-handler.js +1 -1
- package/dist/src/execution/session/turn-step-result.js +1 -1
- package/dist/src/execution/session/turn-step.js +1 -1
- package/dist/src/execution/session/turn-waiting-step.js +1 -1
- package/dist/src/execution/session/turn.js +1 -1
- package/dist/src/execution/session-inbox/protocol.js +1 -1
- package/dist/src/execution/session.d.ts +0 -12
- package/dist/src/execution/session.js +2 -2
- package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
- package/dist/src/execution/tasks/model-step.js +1 -1
- package/dist/src/execution/tasks/start.d.ts +4 -4
- package/dist/src/execution/tasks/start.js +1 -1
- package/dist/src/execution/tasks/steps.js +1 -1
- package/dist/src/execution/terminate-child-sessions-step.js +1 -1
- package/dist/src/execution/tools/agent-router-workflow.js +1 -1
- package/dist/src/execution/tools/workflow/body.d.ts +2 -2
- package/dist/src/execution/workflow-runtime.d.ts +5 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/harness/hitl/approvals.js +1 -1
- package/dist/src/harness/hitl/budget-request.d.ts +12 -6
- package/dist/src/harness/hitl/budget-request.js +1 -1
- package/dist/src/harness/hitl/budget.d.ts +7 -1
- package/dist/src/harness/hitl/budget.js +1 -1
- package/dist/src/harness/hitl/coordinator.js +1 -1
- package/dist/src/harness/hitl/delivery.d.ts +1 -1
- package/dist/src/harness/hitl/delivery.js +1 -1
- package/dist/src/harness/hitl/index.js +1 -1
- package/dist/src/harness/hitl/intake.d.ts +2 -2
- package/dist/src/harness/hitl/intake.js +1 -1
- package/dist/src/harness/messages.d.ts +5 -0
- package/dist/src/harness/messages.js +1 -1
- package/dist/src/harness/model-call/call.d.ts +2 -2
- package/dist/src/harness/model-call/request.d.ts +2 -2
- package/dist/src/harness/session-machine/transitions.d.ts +3 -1
- package/dist/src/harness/session-machine/transitions.js +1 -1
- package/dist/src/harness/step/context.d.ts +3 -0
- package/dist/src/harness/step/context.js +1 -1
- package/dist/src/harness/step/intake.d.ts +5 -2
- package/dist/src/harness/step/intake.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/harness/turn-tag-state.d.ts +7 -26
- package/dist/src/harness/turn-tag-state.js +1 -1
- package/dist/src/harness/types.d.ts +10 -25
- package/dist/src/internal/application/build-profile.d.ts +3 -0
- package/dist/src/internal/application/build-profile.js +1 -1
- package/dist/src/internal/application/compiled-artifacts.js +2 -2
- package/dist/src/internal/application/dev-environment.d.ts +13 -0
- package/dist/src/internal/application/dev-environment.js +1 -1
- package/dist/src/internal/application/stamped-package-version.js +1 -1
- package/dist/src/internal/authored-definition/core.js +1 -1
- package/dist/src/internal/authored-module-build-options.d.ts +7 -0
- package/dist/src/internal/authored-module-build-options.js +1 -0
- package/dist/src/internal/authored-module-graph.d.ts +13 -0
- package/dist/src/internal/authored-module-graph.js +1 -0
- package/dist/src/internal/authored-module-loader.d.ts +22 -1
- package/dist/src/internal/authored-module-loader.js +1 -3
- package/dist/src/internal/authored-module.d.ts +7 -0
- package/dist/src/internal/authored-module.js +1 -1
- package/dist/src/internal/authored-package-boundary.js +1 -1
- package/dist/src/internal/bundler/nitro-rolldown.d.ts +5 -1
- package/dist/src/internal/bundler/nitro-rolldown.js +1 -1
- package/dist/src/internal/development-programmatic-source-loader.d.ts +1 -0
- package/dist/src/internal/development-programmatic-source-loader.js +1 -1
- package/dist/src/internal/nitro/dev-runtime-artifacts.js +1 -1
- package/dist/src/internal/nitro/dev-runtime-generation-metadata.js +1 -1
- package/dist/src/internal/nitro/host/build-application.js +1 -1
- package/dist/src/internal/nitro/host/build-deployment-id.d.ts +8 -0
- package/dist/src/internal/nitro/host/build-deployment-id.js +1 -0
- package/dist/src/internal/nitro/host/create-application-nitro.js +1 -1
- package/dist/src/internal/nitro/host/dev-server-state.js +1 -1
- package/dist/src/internal/nitro/host/skill-server-assets.js +1 -1
- package/dist/src/internal/nitro/host/stage-build-deployment-id.d.ts +2 -0
- package/dist/src/internal/nitro/host/stage-build-deployment-id.js +1 -0
- package/dist/src/internal/nitro/host/start-production-server.d.ts +1 -0
- package/dist/src/internal/nitro/host/start-production-server.js +3 -3
- package/dist/src/internal/nitro/routes/schedule-task.d.ts +3 -0
- package/dist/src/internal/nitro/routes/schedule-task.js +1 -1
- package/dist/src/internal/programmatic-source-loader.d.ts +1 -0
- package/dist/src/internal/programmatic-source-loader.js +1 -1
- package/dist/src/internal/schedules/disabled.d.ts +2 -0
- package/dist/src/internal/schedules/disabled.js +1 -0
- package/dist/src/internal/workflow/development-world-server.js +1 -1
- package/dist/src/internal/workflow/local-world-data-directory.d.ts +2 -1
- package/dist/src/internal/workflow/local-world-data-directory.js +1 -1
- package/dist/src/internal/workflow-bundle/builder.js +1 -1
- package/dist/src/protocol/message.d.ts +27 -1
- package/dist/src/protocol/message.js +2 -2
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +13 -0
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
- package/dist/src/public/channels/chat-sdk/fetch-message.d.ts +10 -0
- package/dist/src/public/channels/chat-sdk/fetch-message.js +1 -0
- package/dist/src/public/channels/chat-sdk/index.d.ts +1 -1
- package/dist/src/public/channels/chat-sdk/reply-context.d.ts +15 -0
- package/dist/src/public/channels/chat-sdk/reply-context.js +2 -0
- package/dist/src/public/channels/discord/discordChannel.d.ts +3 -0
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/github/dispatch.js +1 -1
- package/dist/src/public/channels/github/githubChannel.d.ts +3 -0
- package/dist/src/public/channels/imessage-tapbacks.d.ts +74 -0
- package/dist/src/public/channels/imessage-tapbacks.js +1 -0
- package/dist/src/public/channels/index.d.ts +2 -1
- package/dist/src/public/channels/index.js +1 -1
- package/dist/src/public/channels/linear/linearChannel.d.ts +3 -0
- package/dist/src/public/channels/linear/linearChannel.js +1 -1
- package/dist/src/public/channels/linq/inboundContent.d.ts +7 -0
- package/dist/src/public/channels/linq/inboundContent.js +1 -1
- package/dist/src/public/channels/linq/index.d.ts +1 -0
- package/dist/src/public/channels/linq/index.js +1 -1
- package/dist/src/public/channels/linq/linqChannel.d.ts +17 -1
- package/dist/src/public/channels/linq/linqChannel.js +1 -1
- package/dist/src/public/channels/photon/inboundContent.d.ts +7 -0
- package/dist/src/public/channels/photon/inboundContent.js +2 -1
- package/dist/src/public/channels/photon/index.d.ts +1 -0
- package/dist/src/public/channels/photon/index.js +1 -1
- package/dist/src/public/channels/photon/photonIMessageChannel.d.ts +17 -1
- package/dist/src/public/channels/photon/photonIMessageChannel.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +6 -0
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/model-context.d.ts +7 -0
- package/dist/src/public/channels/slack/model-context.js +1 -1
- package/dist/src/public/channels/slack/reply-delivery.d.ts +5 -0
- package/dist/src/public/channels/slack/reply-delivery.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +17 -3
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/teams/teamsChannel.d.ts +3 -0
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/telegram/telegramChannel.d.ts +3 -0
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/definitions/agent.d.ts +1 -1
- package/dist/src/public/definitions/channel.d.ts +2 -2
- package/dist/src/public/definitions/channel.js +1 -1
- package/dist/src/public/definitions/hook.d.ts +1 -0
- package/dist/src/public/index.d.ts +1 -1
- package/dist/src/public/tools/index.d.ts +1 -1
- package/dist/src/runtime/agent/resolve-model.d.ts +4 -0
- package/dist/src/runtime/agent/resolve-model.js +1 -1
- package/dist/src/runtime/resolve-agent.js +1 -1
- package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
- package/dist/src/runtime/subagents/dynamic-agent-config.d.ts +2 -1
- package/dist/src/runtime/subagents/dynamic-agent-config.js +1 -1
- package/dist/src/runtime/types.d.ts +3 -2
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/shared/agent-definition.d.ts +31 -42
- package/dist/src/shared/session-history.d.ts +22 -0
- package/dist/src/shared/session-history.js +1 -0
- package/dist/src/subagents/tool.d.ts +1 -3
- package/dist/src/subagents/tool.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/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 +14 -1
- package/dist/src/tracing/eve/conversation-context.d.ts +4 -1
- package/dist/src/tracing/eve/conversation-context.js +1 -1
- package/dist/src/tracing/eve/invoke-tool-span.js +1 -1
- package/dist/src/tracing/eve/operation-input.d.ts +0 -5
- package/dist/src/tracing/eve/operation-input.js +1 -1
- package/dist/src/tracing/eve/otel-registration.js +1 -1
- package/dist/src/tracing/eve/session.d.ts +3 -2
- package/dist/src/tracing/eve/session.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/agent-config.md +86 -65
- package/docs/channels/chat-sdk.mdx +6 -1
- package/docs/channels/custom.mdx +38 -6
- package/docs/channels/eve.mdx +46 -0
- package/docs/channels/linq.mdx +51 -0
- package/docs/channels/photon.mdx +64 -0
- package/docs/channels/slack.mdx +4 -2
- package/docs/concepts/built-in-tools.md +1 -1
- package/docs/concepts/execution-model-and-durability.mdx +12 -5
- package/docs/concepts/sessions-runs-and-streaming.md +9 -8
- package/docs/connections/overview.mdx +1 -1
- package/docs/experimental/dynamic-schedules.mdx +186 -0
- package/docs/experimental/meta.json +4 -0
- package/docs/guides/client/streaming.mdx +1 -0
- package/docs/guides/deployment/self-hosting.md +3 -0
- package/docs/guides/hooks.md +21 -1
- package/docs/memory/overview.mdx +2 -1
- package/docs/patterns/dynamic-scheduling.md +3 -1
- package/docs/reference/cli.md +14 -7
- package/docs/schedules.mdx +2 -0
- package/docs/subagents/index.mdx +2 -0
- package/docs/tools/overview.mdx +2 -1
- package/docs/tools/tasks-upgrade.md +12 -7
- package/docs/tools/tasks.md +10 -6
- package/docs/tools/workflows.mdx +36 -5
- package/docs/tutorial/connect-a-warehouse.mdx +1 -1
- package/docs/tutorial/run-analysis.mdx +1 -1
- package/package.json +1 -1
- package/dist/src/execution/run-session-limits.d.ts +0 -12
- package/dist/src/execution/run-session-limits.js +0 -1
- package/dist/src/harness/session-usage-limits.d.ts +0 -49
- package/dist/src/harness/session-usage-limits.js +0 -1
- package/dist/src/subagents/token-budget.d.ts +0 -15
- package/dist/src/subagents/token-budget.js +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { SessionAuthContext } from "#channel/types.js";
|
|
2
|
+
import type { Adapter, Author, Chat, Message, ReactionEvent, Thread } from "#compiled/chat/index.js";
|
|
3
|
+
import type { ChatSdkChannel, ChatSdkChannelBridge } from "#public/channels/chat-sdk/index.js";
|
|
4
|
+
import { type ToolDefinition } from "#public/tools/index.js";
|
|
5
|
+
/** The six standard iMessage tapbacks. */
|
|
6
|
+
export declare const IMESSAGE_TAPBACKS: readonly ["love", "like", "dislike", "laugh", "emphasize", "question"];
|
|
7
|
+
/** A standard iMessage tapback. */
|
|
8
|
+
export type IMessageTapbackType = (typeof IMESSAGE_TAPBACKS)[number];
|
|
9
|
+
/** A tapback someone added to or removed from a message in an iMessage conversation. */
|
|
10
|
+
export interface IMessageTapback {
|
|
11
|
+
/** `true` when the tapback was added, `false` when it was removed. */
|
|
12
|
+
readonly added: boolean;
|
|
13
|
+
/** The person who sent the tapback. */
|
|
14
|
+
readonly author: Author;
|
|
15
|
+
/** The message the tapback is on, when the provider includes it. */
|
|
16
|
+
readonly message?: Message;
|
|
17
|
+
/** Id of the message the tapback is on. */
|
|
18
|
+
readonly messageId: string;
|
|
19
|
+
/** The provider's raw event. */
|
|
20
|
+
readonly raw: unknown;
|
|
21
|
+
/** A standard tapback, or the emoji itself for an emoji tapback. */
|
|
22
|
+
readonly tapback: IMessageTapbackType | (string & {});
|
|
23
|
+
}
|
|
24
|
+
/** Result of an `onTapback` hook. Return `null` to ignore the tapback. */
|
|
25
|
+
export type IMessageTapbackResult = {
|
|
26
|
+
/**
|
|
27
|
+
* Option id that answers the input request eve posted in the message the
|
|
28
|
+
* tapback is on, such as `"approve"` or `"cancel"` for a tool approval.
|
|
29
|
+
* When that message isn't waiting on an answer with this option, the agent
|
|
30
|
+
* receives the tapback as a message instead.
|
|
31
|
+
*/
|
|
32
|
+
readonly answer?: string;
|
|
33
|
+
readonly auth: SessionAuthContext | null;
|
|
34
|
+
readonly context?: readonly string[];
|
|
35
|
+
/**
|
|
36
|
+
* Replaces the text the agent receives. Defaults to text like
|
|
37
|
+
* `Tapback: Loved “See you at 7”`.
|
|
38
|
+
*/
|
|
39
|
+
readonly message?: string;
|
|
40
|
+
/** Overrides the workflow run title without changing the message sent to the model. */
|
|
41
|
+
readonly title?: string;
|
|
42
|
+
} | null;
|
|
43
|
+
/** Input of the tool returned by `tapbackTool()`. */
|
|
44
|
+
export interface IMessageTapbackToolInput {
|
|
45
|
+
/** A standard tapback, or a single emoji such as `"🎉"`. */
|
|
46
|
+
readonly tapback: IMessageTapbackType | (string & {});
|
|
47
|
+
}
|
|
48
|
+
/** Context passed to an `onTapback` hook. */
|
|
49
|
+
export interface IMessageTapbackContext {
|
|
50
|
+
/** Low-level Chat SDK thread for iMessage-specific operations. */
|
|
51
|
+
readonly thread: Thread<unknown>;
|
|
52
|
+
}
|
|
53
|
+
type TapbackHook = (ctx: IMessageTapbackContext, tapback: IMessageTapback) => IMessageTapbackResult | Promise<IMessageTapbackResult>;
|
|
54
|
+
/** Sends an emoji tapback (not one of the six standard ones) through the channel's adapter. */
|
|
55
|
+
export type SendEmojiTapback = (adapter: Adapter, threadId: string, messageId: string, emoji: string) => Promise<void>;
|
|
56
|
+
/** Normalizes a Chat SDK reaction event from an iMessage adapter. */
|
|
57
|
+
export declare function toIMessageTapback(event: ReactionEvent): IMessageTapback;
|
|
58
|
+
/** Model-visible text for a tapback, in the form iMessage uses for tapbacks shown as text. */
|
|
59
|
+
export declare function iMessageTapbackText(tapback: IMessageTapback): string;
|
|
60
|
+
/** Sends tapbacks people add or remove to eve when `onTapback` accepts them. */
|
|
61
|
+
export declare function dispatchIMessageTapbacks<TAdapters extends Record<string, Adapter>>(bridge: ChatSdkChannelBridge<TAdapters>, onTapback: TapbackHook): void;
|
|
62
|
+
/**
|
|
63
|
+
* Builds a tool that reacts to the latest message in the session's iMessage
|
|
64
|
+
* conversation. It acts only in sessions that came through `channel`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function iMessageTapbackTool(options: {
|
|
67
|
+
readonly adapterName: string;
|
|
68
|
+
readonly bot: Chat;
|
|
69
|
+
readonly channel: ChatSdkChannel;
|
|
70
|
+
readonly channelLabel: string;
|
|
71
|
+
/** Defaults to the adapter's `addReaction`. */
|
|
72
|
+
readonly sendEmoji?: SendEmojiTapback;
|
|
73
|
+
}): ToolDefinition<IMessageTapbackToolInput, string>;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadContext}from"#context/container.js";import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{isChannel}from"#public/channels/index.js";import{fetchMessageBestEffort}from"#public/channels/chat-sdk/fetch-message.js";import{defineTool}from"#public/tools/index.js";const IMESSAGE_TAPBACKS=[`love`,`like`,`dislike`,`laugh`,`emphasize`,`question`],TAPBACK_BY_EMOJI_NAME={exclamation:`emphasize`,heart:`love`,laugh:`laugh`,question:`question`,thumbs_down:`dislike`,thumbs_up:`like`},TAPBACK_BY_GLYPH={"❓":`question`,"❤":`love`,"❤️":`love`,"‼":`emphasize`,"‼️":`emphasize`,"👍":`like`,"👎":`dislike`,"😂":`laugh`},EMOJI=/\p{Extended_Pictographic}|\p{Regional_Indicator}/u,graphemes=new Intl.Segmenter(void 0,{granularity:`grapheme`}),ADDED_VERB={dislike:`Disliked`,emphasize:`Emphasized`,laugh:`Laughed at`,like:`Liked`,love:`Loved`,question:`Questioned`};function toIMessageTapback(e){return{added:e.added,author:e.user,message:e.message,messageId:e.messageId,raw:e.raw,tapback:TAPBACK_BY_EMOJI_NAME[e.emoji.name]??e.rawEmoji}}function iMessageTapbackText(e){let t=e.message?.text.trim(),n=t?`“${t.length>200?`${t.slice(0,199)}…`:t}”`:`a message`,r=isStandardTapback(e.tapback);return e.added?r?`Tapback: ${ADDED_VERB[e.tapback]} ${n}`:`Tapback: Reacted ${e.tapback} to ${n}`:`Tapback: Removed ${r?`a ${e.tapback} tapback`:e.tapback} from ${n}`}function dispatchIMessageTapbacks(e,t){e.bot.onReaction(async n=>{if(n.user.isMe)return;let r=toIMessageTapback({...n,message:n.message??await fetchMessageBestEffort(n.adapter,n.threadId,n.messageId)??void 0}),i=await t({thread:n.thread},r);i!==null&&await e.send(i.message??iMessageTapbackText(r),{auth:i.auth,context:[...i.context??[]],inputAnswer:i.answer===void 0?void 0:{messageId:r.messageId,optionId:i.answer},thread:n.thread.toJSON(),title:i.title})})}function iMessageTapbackTool(e){let{adapterName:t,bot:n,channel:r,channelLabel:i}=e,a=e.sendEmoji??((e,t,n,r)=>e.addReaction(t,n,r));return defineTool({availableInSubagents:!1,description:`React to the latest message in this iMessage conversation with a tapback: love (heart), like (thumbs up), dislike (thumbs down), laugh (haha), emphasize (!!), question (?), or any single emoji such as 🎉. Use it to acknowledge a message, alone or alongside a reply.`,inputSchema:{type:`object`,properties:{tapback:{type:`string`,description:`One of ${IMESSAGE_TAPBACKS.join(`, `)}, or a single emoji.`}},required:[`tapback`],additionalProperties:!1},async execute(e){let o=normalizeOutgoingTapback(e.tapback);if(o===void 0)throw Error(`Unknown tapback "${e.tapback}". Use one of ${IMESSAGE_TAPBACKS.join(`, `)}, or a single emoji.`);let s=loadContext().get(ChannelKey);if(s===void 0||!isChannel({kind:s.kind},r))throw Error(`Tapbacks work only in a conversation that came through the ${i} channel.`);let c=s.state,l=c?.thread?.id,u=c?.latestMessageId;if(!l||!u)throw Error(`There is no message in this conversation to react to yet.`);await n.initialize();let d=n.getAdapter(t);return isStandardTapback(o)?await d.addReaction(l,u,o):await a(d,l,u,o),`Sent a ${o} tapback.`}})}function normalizeOutgoingTapback(e){let t=e.trim(),n=t.toLowerCase();if(isStandardTapback(n))return n;let r=TAPBACK_BY_GLYPH[t];return r===void 0?[...graphemes.segment(t)].length===1&&EMOJI.test(t)?t:void 0:r}function isStandardTapback(e){return IMESSAGE_TAPBACKS.includes(e)}export{IMESSAGE_TAPBACKS,dispatchIMessageTapbacks,iMessageTapbackText,iMessageTapbackTool,toIMessageTapback};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { defineChannel, disableRoute, GET, HEAD, OPTIONS, POST, PUT, PATCH, DELETE, WS, type AttachSessionFn, type CancelTurnResult, type ClearSessionResult, type CompactSessionResult, type ResetSessionResult, SessionStrandedError, type Channel, type ChannelAudience, type AudienceCaller, type AudienceContext, type AudienceInput, type AudiencePrincipal, type ConversationEnvironment, type ChannelFrom, type ChannelReceiveContext, type ChannelResolveSession, type ChannelRespondOptions, type ChannelSendOptions, type ChannelSource, type ChannelCors, type ChannelCorsOptions, type ChannelDefinition, type DisabledRouteSentinel, type ChannelContinuationOps, type ChannelEvents, type InferChannelMetadata, type Session, type SessionCallback, type SessionHandle, type SessionRespondOptions, type SessionSendOptions, type TurnPolicy, type RouteDefinition, type RouteHandlerArgs, type AgentDescription, type AgentSkillDescription, type AgentSkillFileDescription, type AgentToolDescription, type InvokeToolFn, type InvokeToolOptions, type InvokeToolResult, type HttpRouteDefinition, type WebSocketMessage, type WebSocketPeer, type WebSocketRouteDefinition, type WebSocketRouteHandler, type WebSocketRouteHooks, type WebSocketUpgradeRequest, type WebSocketUpgradeResult, isDisabledRouteSentinel, } from "#public/definitions/channel.js";
|
|
1
|
+
export { defineChannel, disableRoute, GET, HEAD, OPTIONS, POST, PUT, PATCH, DELETE, WS, type AttachSessionFn, type CancelTurnResult, type ClearSessionResult, type CompactSessionResult, type ResetSessionResult, SessionStrandedError, type Channel, type ChannelAudience, type AudienceCaller, type AudienceContext, type AudienceInput, type AudiencePrincipal, type ConversationEnvironment, type ChannelFrom, type ChannelReceiveContext, type ChannelResolveSession, type ChannelRespondOptions, type ChannelCreateOptions, type ChannelCreateResult, type ChannelSendOptions, type ChannelSource, type ChannelCors, type ChannelCorsOptions, type ChannelDefinition, type DisabledRouteSentinel, type ChannelContinuationOps, type ChannelEvents, type InferChannelMetadata, type Session, type SessionCallback, type SessionHandle, type SessionRespondOptions, type SessionSendOptions, type TurnPolicy, type RouteDefinition, type RouteHandlerArgs, type AgentDescription, type AgentSkillDescription, type AgentSkillFileDescription, type AgentToolDescription, type InvokeToolFn, type InvokeToolOptions, type InvokeToolResult, type HttpRouteDefinition, type WebSocketMessage, type WebSocketPeer, type WebSocketRouteDefinition, type WebSocketRouteHandler, type WebSocketRouteHooks, type WebSocketUpgradeRequest, type WebSocketUpgradeResult, isDisabledRouteSentinel, } from "#public/definitions/channel.js";
|
|
2
2
|
export { attachmentError } from "#public/channels/attachment-error.js";
|
|
3
3
|
export type { FetchFileContext, FetchFileFunction, FetchFileResult, } from "#shared/channel-definition.js";
|
|
4
|
+
export { InvalidSessionHistoryError, type SessionHistoryMessage } from "#shared/session-history.js";
|
|
4
5
|
export { createWebSocketUpgradeServer, type WebSocketUpgradeServerBridge, } from "#channel/websocket-upgrade-server.js";
|
|
5
6
|
import type { Channel, InferChannelMetadata } from "#public/definitions/channel.js";
|
|
6
7
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getChannelInstrumentationKind}from"#channel/compiled-channel.js";import{DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,SessionStrandedError,WS,defineChannel,disableRoute,isDisabledRouteSentinel}from"#public/definitions/channel.js";import{attachmentError}from"#public/channels/attachment-error.js";import{createWebSocketUpgradeServer}from"#channel/websocket-upgrade-server.js";function isChannel(e,t){return e.kind===getChannelInstrumentationKind(t)}export{DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,SessionStrandedError,WS,attachmentError,createWebSocketUpgradeServer,defineChannel,disableRoute,isChannel,isDisabledRouteSentinel};
|
|
1
|
+
import{InvalidSessionHistoryError}from"#shared/session-history.js";import{getChannelInstrumentationKind}from"#channel/compiled-channel.js";import{DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,SessionStrandedError,WS,defineChannel,disableRoute,isDisabledRouteSentinel}from"#public/definitions/channel.js";import{attachmentError}from"#public/channels/attachment-error.js";import{createWebSocketUpgradeServer}from"#channel/websocket-upgrade-server.js";function isChannel(e,t){return e.kind===getChannelInstrumentationKind(t)}export{DELETE,GET,HEAD,InvalidSessionHistoryError,OPTIONS,PATCH,POST,PUT,SessionStrandedError,WS,attachmentError,createWebSocketUpgradeServer,defineChannel,disableRoute,isChannel,isDisabledRouteSentinel};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PromptQueueState } from "#channel/prompt-queue.js";
|
|
2
|
+
import type { SessionHistoryMessage } from "#shared/session-history.js";
|
|
2
3
|
import type { SessionAuthContext, TurnPolicy } from "#channel/types.js";
|
|
3
4
|
import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
|
|
4
5
|
import { createLinearAgentActivity, listLinearAgentSessionActivities, updateLinearAgentSession, type LinearApiOptions } from "#public/channels/linear/api.js";
|
|
@@ -121,6 +122,8 @@ export interface LinearChannelEvents {
|
|
|
121
122
|
export type LinearInboundResult = {
|
|
122
123
|
readonly auth: SessionAuthContext | null;
|
|
123
124
|
readonly context?: readonly string[];
|
|
125
|
+
/** Prior conversation added as user and assistant turns before this message. */
|
|
126
|
+
readonly history?: readonly SessionHistoryMessage[];
|
|
124
127
|
/** Overrides the workflow run title without changing the message sent to the model. */
|
|
125
128
|
readonly title?: string;
|
|
126
129
|
} | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{isObject,readNonEmptyString}from"#shared/guards.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{createLinearAgentActivity,createLinearAgentSessionOnComment,createLinearAgentSessionOnIssue,listLinearAgentSessionActivities,updateLinearAgentSession}from"#public/channels/linear/api.js";import{LINEAR_CHANNEL_DEFAULT_ROUTE}from"#public/channels/linear/constants.js";import{createDefaultEvents,defaultOnAgentSession}from"#public/channels/linear/defaults.js";import{formatLinearContextBlock,linearContinuationToken,messageFromLinearAgentSessionEvent,parseLinearWebhookEvent}from"#public/channels/linear/inbound.js";import{verifyLinearRequest}from"#public/channels/linear/verify.js";import{attachLinearInboundUploads}from"#public/channels/linear/inbound-uploads.js";const log=createLogger(`linear.channel`);function linearChannel(e={}){let t=e.onAgentSession??defaultOnAgentSession,n={...createDefaultEvents({api:e.api,credentials:e.credentials}),...e.events};return defineChannel({kindHint:`linear`,turnPolicy:e.turnPolicy,state:initialLinearState(),metadata(e){return{agentSessionId:e.agentSessionId,commentId:e.commentId??null,issueId:e.issueId??null,issueIdentifier:e.issueIdentifier??null,organizationId:e.organizationId??null}},context(t,n){return rebuildLinearContext(t,n,e)},routes:[POST(e.route??LINEAR_CHANNEL_DEFAULT_ROUTE,async(n,{from:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials,e.maxSkewMs);if(a===null)return new Response(`unauthorized`,{status:401});let o;try{o=parseLinearWebhookEvent({body:a,headers:n.headers})}catch(e){return log.warn(`inbound Linear body is not valid JSON`,{error:e}),jsonOk({ignored:!0,ok:!0})}return o===null?jsonOk({ignored:!0,ok:!0}):o.kind===`agent_session`?(i(dispatchAgentSession({config:e,event:o,from:r,onAgentSession:t})),jsonOk({ok:!0})):e.onDataWebhook===void 0?jsonOk({ignored:!0,ok:!0}):(i(Promise.resolve(e.onDataWebhook(o))),jsonOk({ok:!0}))})],async receive(t,{from:n}){let r=t.target,i=await resolveReceiveSession(r,e),a=readNonEmptyString(r.initialActivity);return a!==void 0&&await createLinearAgentActivity({api:e.api,credentials:e.credentials,activity:{agentSessionId:i.id,content:{body:a,type:`thought`}}}),n(linearContinuationToken(i.id)).send(t.message,{auth:t.auth,state:stateFromAgentSession(i)})},events:n})}function rebuildLinearContext(e,t,n){return{linear:buildLinearHandle({agentSessionId:e.agentSessionId??``,config:n}),state:e}}function buildLinearHandle(e){return{agentSessionId:e.agentSessionId,createActivity(t,n){return createLinearAgentActivity({api:e.config.api,credentials:e.config.credentials,activity:{agentSessionId:e.agentSessionId,content:t,ephemeral:n?.ephemeral,signal:n?.signal,signalMetadata:n?.signalMetadata}})},listActivities(t){return listLinearAgentSessionActivities({api:e.config.api,credentials:e.config.credentials,agentSessionId:e.agentSessionId,last:t?.last})},updateSession(t){return updateLinearAgentSession({api:e.config.api,credentials:e.config.credentials,id:e.agentSessionId,update:t})}}}async function dispatchAgentSession(e){let{event:t}=e,n={delivery:t.delivery,linear:buildLinearHandle({agentSessionId:t.agentSession.id,config:e.config}),session:t.agentSession},r=await e.onAgentSession(n,t);if(r===null)return;let i=await attachLinearInboundUploads({content:messageFromLinearAgentSessionEvent(t),credentials:e.config.credentials,fetch:e.config.api?.fetch});await e.from(linearContinuationToken(t.agentSession.id)).send(i,{auth:r.auth,context:[formatLinearContextBlock(t),...t.previousComments,...r.context??[]],state:stateFromAgentSession(t.agentSession),title:r.title})}async function resolveReceiveSession(e,t){if(hasString(e,`agentSessionId`))return{id:e.agentSessionId};if(hasString(e,`issueId`))return createLinearAgentSessionOnIssue({api:t.api,credentials:t.credentials,externalLink:readNonEmptyString(e.externalLink),externalUrls:readExternalUrls(e.externalUrls),issueId:e.issueId});if(hasString(e,`commentId`))return createLinearAgentSessionOnComment({api:t.api,credentials:t.credentials,commentId:e.commentId,externalLink:readNonEmptyString(e.externalLink),externalUrls:readExternalUrls(e.externalUrls)});throw Error(`linearChannel().receive requires target.agentSessionId, issueId, or commentId.`)}function stateFromAgentSession(e){return{agentSessionId:e.id,agentSessionUrl:e.url??null,commentId:e.commentId??null,issueId:e.issueId??e.issue?.id??null,issueIdentifier:e.issue?.identifier??null,issueTitle:e.issue?.title??null,issueUrl:e.issue?.url??null,organizationId:`organizationId`in e?e.organizationId??null:null,pendingToolCallMessage:null,sourceCommentId:e.sourceCommentId??null}}function initialLinearState(){return{agentSessionId:null,agentSessionUrl:null,commentId:null,issueId:null,issueIdentifier:null,issueTitle:null,issueUrl:null,organizationId:null,pendingToolCallMessage:null,sourceCommentId:null}}async function verifyInbound(e,t,n){try{return await verifyLinearRequest(e,{maxSkewMs:n,webhookSecret:t?.webhookSecret,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`linear inbound verification failed`,{error:e}),null}}function hasString(e,t){return typeof e[t]==`string`&&e[t].length>0}function readExternalUrls(e){if(!Array.isArray(e))return;let t=e.filter(e=>isObject(e)&&typeof e.label==`string`&&typeof e.url==`string`);return t.length>0?t:void 0}function jsonOk(e){return new Response(JSON.stringify(e),{headers:{"content-type":`application/json; charset=utf-8`},status:200})}export{linearChannel};
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{isObject,readNonEmptyString}from"#shared/guards.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{createLinearAgentActivity,createLinearAgentSessionOnComment,createLinearAgentSessionOnIssue,listLinearAgentSessionActivities,updateLinearAgentSession}from"#public/channels/linear/api.js";import{LINEAR_CHANNEL_DEFAULT_ROUTE}from"#public/channels/linear/constants.js";import{createDefaultEvents,defaultOnAgentSession}from"#public/channels/linear/defaults.js";import{formatLinearContextBlock,linearContinuationToken,messageFromLinearAgentSessionEvent,parseLinearWebhookEvent}from"#public/channels/linear/inbound.js";import{verifyLinearRequest}from"#public/channels/linear/verify.js";import{attachLinearInboundUploads}from"#public/channels/linear/inbound-uploads.js";const log=createLogger(`linear.channel`);function linearChannel(e={}){let t=e.onAgentSession??defaultOnAgentSession,n={...createDefaultEvents({api:e.api,credentials:e.credentials}),...e.events};return defineChannel({kindHint:`linear`,turnPolicy:e.turnPolicy,state:initialLinearState(),metadata(e){return{agentSessionId:e.agentSessionId,commentId:e.commentId??null,issueId:e.issueId??null,issueIdentifier:e.issueIdentifier??null,organizationId:e.organizationId??null}},context(t,n){return rebuildLinearContext(t,n,e)},routes:[POST(e.route??LINEAR_CHANNEL_DEFAULT_ROUTE,async(n,{from:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials,e.maxSkewMs);if(a===null)return new Response(`unauthorized`,{status:401});let o;try{o=parseLinearWebhookEvent({body:a,headers:n.headers})}catch(e){return log.warn(`inbound Linear body is not valid JSON`,{error:e}),jsonOk({ignored:!0,ok:!0})}return o===null?jsonOk({ignored:!0,ok:!0}):o.kind===`agent_session`?(i(dispatchAgentSession({config:e,event:o,from:r,onAgentSession:t})),jsonOk({ok:!0})):e.onDataWebhook===void 0?jsonOk({ignored:!0,ok:!0}):(i(Promise.resolve(e.onDataWebhook(o))),jsonOk({ok:!0}))})],async receive(t,{from:n}){let r=t.target,i=await resolveReceiveSession(r,e),a=readNonEmptyString(r.initialActivity);return a!==void 0&&await createLinearAgentActivity({api:e.api,credentials:e.credentials,activity:{agentSessionId:i.id,content:{body:a,type:`thought`}}}),n(linearContinuationToken(i.id)).send(t.message,{auth:t.auth,state:stateFromAgentSession(i)})},events:n})}function rebuildLinearContext(e,t,n){return{linear:buildLinearHandle({agentSessionId:e.agentSessionId??``,config:n}),state:e}}function buildLinearHandle(e){return{agentSessionId:e.agentSessionId,createActivity(t,n){return createLinearAgentActivity({api:e.config.api,credentials:e.config.credentials,activity:{agentSessionId:e.agentSessionId,content:t,ephemeral:n?.ephemeral,signal:n?.signal,signalMetadata:n?.signalMetadata}})},listActivities(t){return listLinearAgentSessionActivities({api:e.config.api,credentials:e.config.credentials,agentSessionId:e.agentSessionId,last:t?.last})},updateSession(t){return updateLinearAgentSession({api:e.config.api,credentials:e.config.credentials,id:e.agentSessionId,update:t})}}}async function dispatchAgentSession(e){let{event:t}=e,n={delivery:t.delivery,linear:buildLinearHandle({agentSessionId:t.agentSession.id,config:e.config}),session:t.agentSession},r=await e.onAgentSession(n,t);if(r===null)return;let i=await attachLinearInboundUploads({content:messageFromLinearAgentSessionEvent(t),credentials:e.config.credentials,fetch:e.config.api?.fetch});await e.from(linearContinuationToken(t.agentSession.id)).send(i,{auth:r.auth,context:[formatLinearContextBlock(t),...t.previousComments,...r.context??[]],history:r.history,state:stateFromAgentSession(t.agentSession),title:r.title})}async function resolveReceiveSession(e,t){if(hasString(e,`agentSessionId`))return{id:e.agentSessionId};if(hasString(e,`issueId`))return createLinearAgentSessionOnIssue({api:t.api,credentials:t.credentials,externalLink:readNonEmptyString(e.externalLink),externalUrls:readExternalUrls(e.externalUrls),issueId:e.issueId});if(hasString(e,`commentId`))return createLinearAgentSessionOnComment({api:t.api,credentials:t.credentials,commentId:e.commentId,externalLink:readNonEmptyString(e.externalLink),externalUrls:readExternalUrls(e.externalUrls)});throw Error(`linearChannel().receive requires target.agentSessionId, issueId, or commentId.`)}function stateFromAgentSession(e){return{agentSessionId:e.id,agentSessionUrl:e.url??null,commentId:e.commentId??null,issueId:e.issueId??e.issue?.id??null,issueIdentifier:e.issue?.identifier??null,issueTitle:e.issue?.title??null,issueUrl:e.issue?.url??null,organizationId:`organizationId`in e?e.organizationId??null:null,pendingToolCallMessage:null,sourceCommentId:e.sourceCommentId??null}}function initialLinearState(){return{agentSessionId:null,agentSessionUrl:null,commentId:null,issueId:null,issueIdentifier:null,issueTitle:null,issueUrl:null,organizationId:null,pendingToolCallMessage:null,sourceCommentId:null}}async function verifyInbound(e,t,n){try{return await verifyLinearRequest(e,{maxSkewMs:n,webhookSecret:t?.webhookSecret,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`linear inbound verification failed`,{error:e}),null}}function hasString(e,t){return typeof e[t]==`string`&&e[t].length>0}function readExternalUrls(e){if(!Array.isArray(e))return;let t=e.filter(e=>isObject(e)&&typeof e.label==`string`&&typeof e.url==`string`);return t.length>0?t:void 0}function jsonOk(e){return new Response(JSON.stringify(e),{headers:{"content-type":`application/json; charset=utf-8`},status:200})}export{linearChannel};
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { UserContent } from "ai";
|
|
2
2
|
import type { Attachment, Message } from "#compiled/chat/index.js";
|
|
3
|
+
import { type MessageLookup } from "#public/channels/chat-sdk/fetch-message.js";
|
|
4
|
+
import type { QuotedMessage } from "#public/channels/chat-sdk/reply-context.js";
|
|
3
5
|
/** Returns model-visible Linq content, or `undefined` for blank messages. */
|
|
4
6
|
export declare function linqInboundContent(message: Message): string | UserContent | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the message a Linq reply quotes. The webhook names only the target's
|
|
9
|
+
* id, so this fetches its text; a failed or slow lookup still reports the reply.
|
|
10
|
+
*/
|
|
11
|
+
export declare function linqQuotedMessage(adapter: MessageLookup, message: Message): Promise<QuotedMessage | undefined>;
|
|
5
12
|
/**
|
|
6
13
|
* Rebuilds a Linq attachment's download after the message crosses the queue.
|
|
7
14
|
* The Linq adapter downloads from its CDN URL without credentials but has no
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_UPLOAD_POLICY}from"#public/channels/upload-policy.js";import{readLimitedBytes}from"#internal/attachments/limited-read.js";import{messageToUserContent}from"#public/channels/chat-sdk/index.js";function linqInboundContent(e){let t=messageToUserContent(e);return typeof t==`string`?t.trim().length>0?t:void 0:t.length>0?t:void 0}function rehydrateLinqAttachment(e){let{url:t}=e;return t?{...e,async fetchData(){let e=await fetch(t);if(!e.ok)throw Error(`Linq CDN returned HTTP ${e.status}.`);return await readLimitedBytes(e,DEFAULT_UPLOAD_POLICY.maxBytes,`linq`)}}:e}export{linqInboundContent,rehydrateLinqAttachment};
|
|
1
|
+
import{DEFAULT_UPLOAD_POLICY}from"#public/channels/upload-policy.js";import{readLimitedBytes}from"#internal/attachments/limited-read.js";import{fetchMessageBestEffort}from"#public/channels/chat-sdk/fetch-message.js";import{messageToUserContent}from"#public/channels/chat-sdk/index.js";function linqInboundContent(e){let t=messageToUserContent(e);return typeof t==`string`?t.trim().length>0?t:void 0:t.length>0?t:void 0}async function linqQuotedMessage(e,t){let n=t.raw?.reply_to?.message_id;if(typeof n!=`string`||n.length===0)return;let r=await fetchMessageBestEffort(e,t.threadId,n);return r===null?{id:n}:{fromAgent:r.author.isMe,id:n,text:r.text}}function rehydrateLinqAttachment(e){let{url:t}=e;return t?{...e,async fetchData(){let e=await fetch(t);if(!e.ok)throw Error(`Linq CDN returned HTTP ${e.status}.`);return await readLimitedBytes(e,DEFAULT_UPLOAD_POLICY.maxBytes,`linq`)}}:e}export{linqInboundContent,linqQuotedMessage,rehydrateLinqAttachment};
|
|
@@ -1 +1,2 @@
|
|
|
1
1
|
export { defaultLinqAuth, linqChannel, type LinqChannel, type LinqChannelConfig, type LinqChannelCredentials, type LinqCredentialValue, type LinqInboundMessageContext, type LinqInboundResult, type LinqInboundResultOrPromise, } from "#public/channels/linq/linqChannel.js";
|
|
2
|
+
export { IMESSAGE_TAPBACKS, type IMessageTapback, type IMessageTapbackContext, type IMessageTapbackResult, type IMessageTapbackToolInput, type IMessageTapbackType, } from "#public/channels/imessage-tapbacks.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defaultLinqAuth,linqChannel}from"#public/channels/linq/linqChannel.js";export{defaultLinqAuth,linqChannel};
|
|
1
|
+
import{defaultLinqAuth,linqChannel}from"#public/channels/linq/linqChannel.js";import{IMESSAGE_TAPBACKS}from"#public/channels/imessage-tapbacks.js";export{IMESSAGE_TAPBACKS,defaultLinqAuth,linqChannel};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { SessionAuthContext, TurnPolicy } from "#channel/types.js";
|
|
2
|
+
import type { SessionHistoryMessage } from "#shared/session-history.js";
|
|
2
3
|
import { type ChatSdkChannelEvents, type ChatSdkChannelState, type ChatSdkInstrumentationMetadata, type ChatSdkReceiveTarget } from "#public/channels/chat-sdk/index.js";
|
|
3
4
|
import type { Channel } from "#public/definitions/channel.js";
|
|
5
|
+
import { type IMessageTapback, type IMessageTapbackContext, type IMessageTapbackResult, type IMessageTapbackToolInput } from "#public/channels/imessage-tapbacks.js";
|
|
6
|
+
import type { ToolDefinition } from "#public/tools/index.js";
|
|
4
7
|
import { createLinqAdapter, type LinqWebhookVerifier } from "#compiled/@linqapp/chat-sdk-adapter/index.js";
|
|
5
8
|
import type { Message, Thread } from "#compiled/chat/index.js";
|
|
6
9
|
/** Context passed to {@link LinqChannelConfig.onMessage}. */
|
|
@@ -12,6 +15,8 @@ export interface LinqInboundMessageContext {
|
|
|
12
15
|
export type LinqInboundResult = {
|
|
13
16
|
readonly auth: SessionAuthContext | null;
|
|
14
17
|
readonly context?: readonly string[];
|
|
18
|
+
/** Prior conversation added as user and assistant turns before this message. */
|
|
19
|
+
readonly history?: readonly SessionHistoryMessage[];
|
|
15
20
|
/** Overrides the workflow run title without changing the message sent to the model. */
|
|
16
21
|
readonly title?: string;
|
|
17
22
|
} | null;
|
|
@@ -40,6 +45,12 @@ export interface LinqChannelConfig {
|
|
|
40
45
|
}>;
|
|
41
46
|
/** Inbound message policy. Defaults to dispatching with the Linq message author's user auth. */
|
|
42
47
|
readonly onMessage?: (ctx: LinqInboundMessageContext, message: Message) => LinqInboundResultOrPromise;
|
|
48
|
+
/**
|
|
49
|
+
* Inbound tapback policy. Tapbacks are ignored by default. Return a result to
|
|
50
|
+
* send the tapback to the agent as a message such as `Tapback: Loved “See you at 7”`,
|
|
51
|
+
* or `null` to ignore it.
|
|
52
|
+
*/
|
|
53
|
+
readonly onTapback?: (ctx: IMessageTapbackContext, tapback: IMessageTapback) => IMessageTapbackResult | Promise<IMessageTapbackResult>;
|
|
43
54
|
/** Override the default webhook route (`/eve/v1/linq`). */
|
|
44
55
|
readonly route?: string;
|
|
45
56
|
/** Policy for accepted messages that arrive while a turn is active. */
|
|
@@ -49,6 +60,11 @@ export interface LinqChannelConfig {
|
|
|
49
60
|
}
|
|
50
61
|
/** First-class eve channel backed by Linq iMessage and SMS. */
|
|
51
62
|
export interface LinqChannel extends Channel<ChatSdkChannelState, ChatSdkReceiveTarget, ChatSdkInstrumentationMetadata> {
|
|
63
|
+
/**
|
|
64
|
+
* Returns a tool that lets the model react to the latest message in the
|
|
65
|
+
* conversation with a tapback. Export it from a file in `agent/tools/`.
|
|
66
|
+
*/
|
|
67
|
+
tapbackTool(): ToolDefinition<IMessageTapbackToolInput, string>;
|
|
52
68
|
}
|
|
53
69
|
/**
|
|
54
70
|
* Creates an eve channel for Linq-powered iMessage and SMS conversations.
|
|
@@ -65,4 +81,4 @@ export interface LinqChannel extends Channel<ChatSdkChannelState, ChatSdkReceive
|
|
|
65
81
|
*/
|
|
66
82
|
export declare function linqChannel(config: LinqChannelConfig): LinqChannel;
|
|
67
83
|
/** Default Linq auth projection for inbound Chat SDK message authors. */
|
|
68
|
-
export declare function defaultLinqAuth(message: Message): SessionAuthContext;
|
|
84
|
+
export declare function defaultLinqAuth(message: Pick<Message, "author">): SessionAuthContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{vercelOidc}from"#public/channels/auth.js";import{chatSdkChannel}from"#public/channels/chat-sdk/index.js";import{createMemoryState}from"#compiled/@chat-adapter/state-memory/index.js";import{createLinqAdapter}from"#compiled/@linqapp/chat-sdk-adapter/index.js";import{linqInboundContent,rehydrateLinqAttachment}from"#public/channels/linq/inboundContent.js";function linqChannel(e){let t=normalizeCredentials(e.credentials);e.baseURL!==void 0&&(t.baseURL=e.baseURL);let n=createLinqAdapter(t);n.rehydrateAttachment=rehydrateLinqAttachment;let r=chatSdkChannel({adapters:{linq:n},concurrency:`concurrent`,events:e.events,routes:{linq:e.route??`/eve/v1/linq`},state:createMemoryState(),streaming:!1,turnPolicy:e.turnPolicy,userName:e.userName??`eve`}),i=e.onMessage??defaultOnMessage;
|
|
1
|
+
import{vercelOidc}from"#public/channels/auth.js";import{chatSdkChannel}from"#public/channels/chat-sdk/index.js";import{dispatchIMessageTapbacks,iMessageTapbackTool}from"#public/channels/imessage-tapbacks.js";import{createMemoryState}from"#compiled/@chat-adapter/state-memory/index.js";import{createLinqAdapter}from"#compiled/@linqapp/chat-sdk-adapter/index.js";import{formatReplyContext}from"#public/channels/chat-sdk/reply-context.js";import{linqInboundContent,linqQuotedMessage,rehydrateLinqAttachment}from"#public/channels/linq/inboundContent.js";function linqChannel(e){let t=normalizeCredentials(e.credentials);e.baseURL!==void 0&&(t.baseURL=e.baseURL);let n=createLinqAdapter(t);n.rehydrateAttachment=rehydrateLinqAttachment;let r=chatSdkChannel({adapters:{linq:n},concurrency:`concurrent`,events:e.events,routes:{linq:e.route??`/eve/v1/linq`},state:createMemoryState(),streaming:!1,turnPolicy:e.turnPolicy,userName:e.userName??`eve`}),i=e.onMessage??defaultOnMessage;r.bot.onDirectMessage(async(e,t)=>{await dispatchMessage(r,i,e,t)}),r.bot.onNewMessage(/[\s\S]*/,async(e,t)=>{await dispatchMessage(r,i,e,t)}),e.onTapback&&dispatchIMessageTapbacks(r,e.onTapback);let a=Object.assign(r.channel,{tapbackTool:()=>iMessageTapbackTool({adapterName:`linq`,bot:r.bot,channel:a,channelLabel:`Linq`})});return a}function normalizeCredentials(e){let{apiKey:t,signingSecret:n,webhookVerifier:r}=e??{},i={};return t!==void 0&&(i.credentials=createCredentialProvider(t,n)),r===void 0?n===void 0&&(i.webhookVerifier=vercelOidc()):i.webhookVerifier=r,i}function createCredentialProvider(e,t){return async()=>{let n={apiKey:await resolveCredentialValue(e)};return t!==void 0&&(n.signingSecret=await resolveCredentialValue(t)),n}}async function resolveCredentialValue(e){return typeof e==`function`?await e():e}function defaultLinqAuth(e){let t={};return e.author.userName!==void 0&&(t.user_name=e.author.userName),{attributes:t,authenticator:`linq-message`,issuer:`linq`,principalId:`linq:${e.author.userId}`,principalType:e.author.isBot?`service`:`user`,subject:e.author.userId}}async function defaultOnMessage(e,t){return{auth:defaultLinqAuth(t)}}async function dispatchMessage(e,t,n,r){let i=await t({thread:n},r);if(i===null)return;let a=e.bot.getAdapter(`linq`),o=linqInboundContent(r),[,s]=await Promise.all([markReadBestEffort(a,n,r),o===void 0?void 0:linqQuotedMessage(a,r)]);o!==void 0&&await e.send(o,{auth:i.auth,context:[...i.context??[],...s?[formatReplyContext(s,r.text)]:[]],history:i.history,thread:n,title:i.title})}async function markReadBestEffort(e,t,n){try{await e.markRead(t.id,n.id)}catch{}}export{defaultLinqAuth,linqChannel};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { UserContent } from "ai";
|
|
2
2
|
import type { Message } from "#compiled/chat/index.js";
|
|
3
|
+
import type { QuotedMessage } from "#public/channels/chat-sdk/reply-context.js";
|
|
3
4
|
/** Returns model-visible Photon content, or `undefined` for non-message events. */
|
|
4
5
|
export declare function photonInboundContent(message: Message): string | UserContent | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Returns the message a Photon iMessage reply quotes. The adapter flattens a
|
|
8
|
+
* reply to its new text, so this reads the target from the raw message. Only a
|
|
9
|
+
* gateway delivery says who sent the target, through its `direction`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function photonQuotedMessage(message: Message): QuotedMessage | undefined;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{messageToUserContent}from"#public/channels/chat-sdk/index.js";function photonInboundContent(e){let t=messageToUserContent(e);return typeof t==`string`?t.trim().length>0?t:void 0:t.length>0?t:void 0}
|
|
1
|
+
import{messageToUserContent}from"#public/channels/chat-sdk/index.js";function photonInboundContent(e){let t=messageToUserContent(e);return typeof t==`string`?t.trim().length>0?t:void 0:t.length>0?t:void 0}function photonQuotedMessage(e){let t=e.raw?.content;if(t?.type!==`reply`)return;let n=t.target;if(typeof n?.id!=`string`||n.id.length===0)return;let r=typeof n.contentPreview==`string`?n.contentPreview:spectrumText(n.content),i={id:n.id};return r!==void 0&&(i.text=r),n.direction===`outbound`&&(i.fromAgent=!0),n.direction===`inbound`&&(i.fromAgent=!1),i}function spectrumText(e){if(e?.type===`text`)return typeof e.text==`string`?e.text:void 0;if(e?.type===`reply`)return spectrumText(e.content);if(e?.type!==`group`||!Array.isArray(e.items))return;let t=e.items.flatMap(e=>{let t=spectrumText(e?.content);return t?[t]:[]});return t.length>0?t.join(`
|
|
2
|
+
`):void 0}export{photonInboundContent,photonQuotedMessage};
|
|
@@ -1 +1,2 @@
|
|
|
1
1
|
export { defaultPhotonAuth, photonIMessageChannel, type PhotonIMessageChannel, type PhotonIMessageChannelConfig, type PhotonIMessageChannelCredentials, type PhotonInboundMessageContext, type PhotonInboundResult, type PhotonInboundResultOrPromise, } from "#public/channels/photon/photonIMessageChannel.js";
|
|
2
|
+
export { IMESSAGE_TAPBACKS, type IMessageTapback, type IMessageTapbackContext, type IMessageTapbackResult, type IMessageTapbackToolInput, type IMessageTapbackType, } from "#public/channels/imessage-tapbacks.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defaultPhotonAuth,photonIMessageChannel}from"#public/channels/photon/photonIMessageChannel.js";export{defaultPhotonAuth,photonIMessageChannel};
|
|
1
|
+
import{IMESSAGE_TAPBACKS}from"#public/channels/imessage-tapbacks.js";import{defaultPhotonAuth,photonIMessageChannel}from"#public/channels/photon/photonIMessageChannel.js";export{IMESSAGE_TAPBACKS,defaultPhotonAuth,photonIMessageChannel};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { SessionAuthContext, TurnPolicy } from "#channel/types.js";
|
|
2
|
+
import type { SessionHistoryMessage } from "#shared/session-history.js";
|
|
2
3
|
import { type ChatSdkChannel, type ChatSdkChannelEvents } from "#public/channels/chat-sdk/index.js";
|
|
4
|
+
import { type IMessageTapback, type IMessageTapbackContext, type IMessageTapbackResult, type IMessageTapbackToolInput } from "#public/channels/imessage-tapbacks.js";
|
|
5
|
+
import type { ToolDefinition } from "#public/tools/index.js";
|
|
3
6
|
import type { Message, Thread } from "#compiled/chat/index.js";
|
|
4
7
|
import { type iMessageAdapter, type iMessageCredentialProvider, type iMessageWebhookVerifier } from "#compiled/@photon-ai/chat-adapter-imessage/index.js";
|
|
5
8
|
/** Photon project credentials used by {@link photonIMessageChannel}. */
|
|
@@ -13,6 +16,8 @@ export interface PhotonInboundMessageContext {
|
|
|
13
16
|
export type PhotonInboundResult = {
|
|
14
17
|
readonly auth: SessionAuthContext | null;
|
|
15
18
|
readonly context?: readonly string[];
|
|
19
|
+
/** Prior conversation added as user and assistant turns before this message. */
|
|
20
|
+
readonly history?: readonly SessionHistoryMessage[];
|
|
16
21
|
/** Overrides the workflow run title without changing the message sent to the model. */
|
|
17
22
|
readonly title?: string;
|
|
18
23
|
} | null;
|
|
@@ -28,6 +33,12 @@ export interface PhotonIMessageChannelConfig {
|
|
|
28
33
|
}>;
|
|
29
34
|
/** Inbound message policy. Defaults to dispatching with the Photon message author's user auth. */
|
|
30
35
|
readonly onMessage?: (ctx: PhotonInboundMessageContext, message: Message) => PhotonInboundResultOrPromise;
|
|
36
|
+
/**
|
|
37
|
+
* Inbound tapback policy. Tapbacks are ignored by default. Return a result to
|
|
38
|
+
* send the tapback to the agent as a message such as `Tapback: Loved “See you at 7”`,
|
|
39
|
+
* or `null` to ignore it.
|
|
40
|
+
*/
|
|
41
|
+
readonly onTapback?: (ctx: IMessageTapbackContext, tapback: IMessageTapback) => IMessageTapbackResult | Promise<IMessageTapbackResult>;
|
|
31
42
|
/** Override the default webhook route (`/eve/v1/photon`). */
|
|
32
43
|
readonly route?: string;
|
|
33
44
|
/** Policy for accepted messages that arrive while a turn is active. */
|
|
@@ -41,6 +52,11 @@ export interface PhotonIMessageChannelConfig {
|
|
|
41
52
|
}
|
|
42
53
|
/** First-class eve channel backed by Photon iMessage. */
|
|
43
54
|
export interface PhotonIMessageChannel extends ChatSdkChannel {
|
|
55
|
+
/**
|
|
56
|
+
* Returns a tool that lets the model react to the latest message in the
|
|
57
|
+
* conversation with a tapback. Export it from a file in `agent/tools/`.
|
|
58
|
+
*/
|
|
59
|
+
tapbackTool(): ToolDefinition<IMessageTapbackToolInput, string>;
|
|
44
60
|
}
|
|
45
61
|
/**
|
|
46
62
|
* Creates an eve channel for Photon-powered iMessage.
|
|
@@ -57,4 +73,4 @@ export interface PhotonIMessageChannel extends ChatSdkChannel {
|
|
|
57
73
|
*/
|
|
58
74
|
export declare function photonIMessageChannel(config: PhotonIMessageChannelConfig): PhotonIMessageChannel;
|
|
59
75
|
/** Default Photon auth projection for inbound Chat SDK message authors. */
|
|
60
|
-
export declare function defaultPhotonAuth(message: Message): SessionAuthContext;
|
|
76
|
+
export declare function defaultPhotonAuth(message: Pick<Message, "author">): SessionAuthContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{vercelOidc}from"#public/channels/auth.js";import{chatSdkChannel}from"#public/channels/chat-sdk/index.js";import{createMemoryState}from"#compiled/@chat-adapter/state-memory/index.js";import{createiMessageAdapter}from"#compiled/@photon-ai/chat-adapter-imessage/index.js";import{addPhotonAttachmentDownloads,addPhotonAttachmentRehydration}from"#public/channels/photon/attachments.js";import{photonInboundContent}from"#public/channels/photon/inboundContent.js";function photonIMessageChannel(e){let t=e.webhookSecret??process.env.IMESSAGE_WEBHOOK_SECRET,n=createiMessageAdapter({credentials:e.credentials,...e.webhookVerifier?{webhookVerifier:e.webhookVerifier}:t?{webhookSecret:t}:{webhookVerifier:vercelOidc()}});addPhotonAttachmentRehydration(n);let r=chatSdkChannel({adapters:{imessage:n},concurrency:`concurrent`,events:e.events,routes:{imessage:e.route??`/eve/v1/photon`},state:createMemoryState(),streaming:!1,turnPolicy:e.turnPolicy,userName:e.userName??`eve`}),i=e.onMessage??defaultOnMessage;
|
|
1
|
+
import{vercelOidc}from"#public/channels/auth.js";import{chatSdkChannel}from"#public/channels/chat-sdk/index.js";import{dispatchIMessageTapbacks,iMessageTapbackTool}from"#public/channels/imessage-tapbacks.js";import{createMemoryState}from"#compiled/@chat-adapter/state-memory/index.js";import{formatReplyContext}from"#public/channels/chat-sdk/reply-context.js";import{createiMessageAdapter}from"#compiled/@photon-ai/chat-adapter-imessage/index.js";import{addPhotonAttachmentDownloads,addPhotonAttachmentRehydration}from"#public/channels/photon/attachments.js";import{photonInboundContent,photonQuotedMessage}from"#public/channels/photon/inboundContent.js";function photonIMessageChannel(e){let t=e.webhookSecret??process.env.IMESSAGE_WEBHOOK_SECRET,n=createiMessageAdapter({credentials:e.credentials,...e.webhookVerifier?{webhookVerifier:e.webhookVerifier}:t?{webhookSecret:t}:{webhookVerifier:vercelOidc()}});addPhotonAttachmentRehydration(n);let r=chatSdkChannel({adapters:{imessage:n},concurrency:`concurrent`,events:e.events,routes:{imessage:e.route??`/eve/v1/photon`},state:createMemoryState(),streaming:!1,turnPolicy:e.turnPolicy,userName:e.userName??`eve`}),i=e.onMessage??defaultOnMessage;r.bot.onDirectMessage(async(e,t)=>{await dispatchMessage(r,i,e,t)}),r.bot.onNewMessage(/[\s\S]*/,async(e,t)=>{await dispatchMessage(r,i,e,t)}),e.onTapback&&dispatchIMessageTapbacks(r,e.onTapback);let a=Object.assign(r.channel,{tapbackTool:()=>iMessageTapbackTool({adapterName:`imessage`,bot:r.bot,channel:a,channelLabel:`Photon iMessage`,sendEmoji:sendPhotonEmojiTapback})});return a}async function sendPhotonEmojiTapback(e,t,n,r){let i=(await e.fetchMessage?.(t,n))?.raw;if(typeof i?.react!=`function`)throw Error(`Photon could not find the message to react to.`);await i.react(r)}function defaultPhotonAuth(e){let t={};return e.author.userName!==void 0&&(t.user_name=e.author.userName),{attributes:t,authenticator:`photon-imessage`,issuer:`photon`,principalId:`photon:${e.author.userId}`,principalType:e.author.isBot?`service`:`user`,subject:e.author.userId}}async function defaultOnMessage(e,t){return{auth:defaultPhotonAuth(t)}}async function dispatchMessage(e,t,n,r){let i=await t({thread:n},r);if(i===null)return;let a=e.bot.getAdapter(`imessage`);await markReadBestEffort(a,n,r),await addPhotonAttachmentDownloads(r,a);let o=photonInboundContent(r);if(o===void 0)return;let s=photonQuotedMessage(r);await e.send(o,{idempotencyKey:r.id,auth:i.auth,context:[...i.context??[],...s?[formatReplyContext(s,r.text)]:[]],history:i.history,thread:n,title:i.title})}async function markReadBestEffort(e,t,n){try{await e.markRead(t.id,n.id)}catch{}}export{defaultPhotonAuth,photonIMessageChannel};
|
|
@@ -321,6 +321,11 @@ export declare function fetchSlackThreadWindow(thread: SlackThread, latest: stri
|
|
|
321
321
|
* live `threadTs` and firing `onThreadTsChanged` so the caller can
|
|
322
322
|
* persist the anchor. Ephemerals and upload-only file posts do not
|
|
323
323
|
* anchor.
|
|
324
|
+
*
|
|
325
|
+
* `onPosted` fires after each reply this binding posts into its existing
|
|
326
|
+
* thread, through `thread.post`, `uploadFiles`, or
|
|
327
|
+
* `request("chat.postMessage")`. A post that anchors a new thread is its
|
|
328
|
+
* root, not a reply, so it does not fire.
|
|
324
329
|
*/
|
|
325
330
|
export declare function buildSlackBinding(input: {
|
|
326
331
|
/** Slack app id used to identify this app's fetched thread replies. */
|
|
@@ -341,5 +346,6 @@ export declare function buildSlackBinding(input: {
|
|
|
341
346
|
/** Actor/content workspace exposed on the public handle. */
|
|
342
347
|
readonly teamId: string | undefined;
|
|
343
348
|
readonly onThreadTsChanged?: (ts: string) => void;
|
|
349
|
+
readonly onPosted?: () => void;
|
|
344
350
|
}): SlackBinding;
|
|
345
351
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{isCardElement}from"#compiled/chat/index.js";import{fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{cardToBlocks,cardToFallbackText}from"#public/channels/slack/blocks.js";import{resolveSlackInboundMrkdwn}from"#public/channels/slack/inbound-content.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";import{callSlackApiTrackingResponse,slackApiOptions}from"#public/channels/slack/transport.js";const log=createLogger(`slack.api`);function slackContinuationToken(e,t){return`${e}:${t}`}async function resolveSlackBotToken(e,t={}){let n=e??process.env.SLACK_BOT_TOKEN;if(!n)throw Error(`SLACK_BOT_TOKEN is required.`);return typeof n==`function`?n(t):resolveSlackBotToken$1(n)}async function callSlackApi(e){return callSlackApiTrackingResponse(e.operation,normalizeSlackApiBody(e.body),slackApiOptions(e.api,()=>resolveSlackBotToken(e.botToken,e.context)))}function createSlackRequester(e,t,n){return(r,i)=>callSlackApi({api:e,botToken:t,context:n,operation:r,body:i})}function buildSlackWorkspaceHandle(e){return{teamId:e.teamId,request:createSlackRequester(e.api,e.botToken,{teamId:e.installationTeamId})}}const THREAD_REFRESH_LIMIT=50,threadWindowFetchers=new WeakMap;async function fetchSlackThreadWindow(e,t,n){return await threadWindowFetchers.get(e)?.(t,n)}function buildSlackBinding(e){let t={teamId:e.installationTeamId},n=slackApiOptions(e.api,()=>resolveSlackBotToken(e.botToken,t)),r=createSlackRequester(e.api,e.botToken,t),i=[],a=e.threadTs,o=e.threadHasReplies??a!==``,s;function handleMessageTs(t){a||t===a||(a=t,e.onThreadTsChanged?.(t))}async function uploadFiles(t,r){let i=r?.channelId??e.channelId,o=r?.threadTs??a,s=t.map(e=>toSlackFileUpload(e,r?.snippetType))
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{isCardElement}from"#compiled/chat/index.js";import{fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{cardToBlocks,cardToFallbackText}from"#public/channels/slack/blocks.js";import{resolveSlackInboundMrkdwn}from"#public/channels/slack/inbound-content.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";import{callSlackApiTrackingResponse,slackApiOptions}from"#public/channels/slack/transport.js";const log=createLogger(`slack.api`);function slackContinuationToken(e,t){return`${e}:${t}`}async function resolveSlackBotToken(e,t={}){let n=e??process.env.SLACK_BOT_TOKEN;if(!n)throw Error(`SLACK_BOT_TOKEN is required.`);return typeof n==`function`?n(t):resolveSlackBotToken$1(n)}async function callSlackApi(e){return callSlackApiTrackingResponse(e.operation,normalizeSlackApiBody(e.body),slackApiOptions(e.api,()=>resolveSlackBotToken(e.botToken,e.context)))}function createSlackRequester(e,t,n){return(r,i)=>callSlackApi({api:e,botToken:t,context:n,operation:r,body:i})}function buildSlackWorkspaceHandle(e){return{teamId:e.teamId,request:createSlackRequester(e.api,e.botToken,{teamId:e.installationTeamId})}}const THREAD_REFRESH_LIMIT=50,threadWindowFetchers=new WeakMap;async function fetchSlackThreadWindow(e,t,n){return await threadWindowFetchers.get(e)?.(t,n)}function buildSlackBinding(e){let t={teamId:e.installationTeamId},n=slackApiOptions(e.api,()=>resolveSlackBotToken(e.botToken,t)),r=createSlackRequester(e.api,e.botToken,t),request=async(t,n)=>{let i=await r(t,n);if(t===`chat.postMessage`&&i.ok===!0){let t=n;a&&t.channel===e.channelId&&t.thread_ts===a&&e.onPosted?.()}return i},i=[],a=e.threadTs,o=e.threadHasReplies??a!==``,s;function handleMessageTs(t){a||t===a||(a=t,e.onThreadTsChanged?.(t))}async function uploadFiles(t,r){let i=r?.channelId??e.channelId,o=r?.threadTs??a,s=t.map(e=>toSlackFileUpload(e,r?.snippetType)),c=await uploadSlackFiles(s,{...n,channelId:i||void 0,initialComment:r?.initialComment,threadTs:o||void 0});return o&&i===e.channelId&&o===a&&e.onPosted?.(),c}function refreshMessages(){if(s!==void 0)return s;async function fetchAndReplace(){if(!e.channelId||!a){i=[];return}try{i=(await fetchSlackThreadReplies({...n,channel:e.channelId,limit:50,ts:a})).messages.map(t=>parseThreadMessage(t,a,{appId:e.appId,botUserId:e.botUserId}))}catch(t){logError(log,`refresh threw — swallowed`,t,{channelId:e.channelId})}}let t=fetchAndReplace().finally(()=>{s===t&&(s=void 0)});return s=t,t}async function fetchWindow(t,r){if(!e.channelId||!a)return;let i=[],o;for(let s=0;s<5;s+=1){let s=await fetchSlackThreadReplies({...n,channel:e.channelId,cursor:o,inclusive:!0,latest:t,limit:200,ts:a});if(i=[...i,...s.messages].slice(-r),o=s.nextCursor,o===void 0||o===``)return i.map(t=>parseThreadMessage(t,a,{appId:e.appId,botUserId:e.botUserId}))}}let c={get recentMessages(){return i},async listParticipants(){await c.refresh();let e=new Set;for(let t of i)t.user!==void 0&&t.botId===void 0&&e.add(t.user);return[...e]},async post(t){let r=normalizePostInput(t),i=r.files??[],s=`blocks`in r||`card`in r||`markdown`in r;if(i.length>0&&!s){let e=await uploadFiles(i,{initialComment:`text`in r?r.text:void 0});return a&&(o=!0),{id:Array.isArray(e.raw.files)&&e.raw.files.length>0?String(e.raw.files[0].id??``):``,raw:e.raw}}let c=a,l=await postSlackMessage(buildPostMessageOptions(r,e.channelId,c,n)),u=l.id;if(c&&e.onPosted?.(),a&&(o=!0),handleMessageTs(u),i.length>0&&s)try{await uploadFiles(i)}catch(e){log.warn(`file upload after message post failed`,{error:e})}return{id:u,raw:l.raw}},async postEphemeral(t,r){let i=normalizePostInput(r),s=o?a:``,c=await postSlackEphemeral({...buildPostMessageOptions(i,e.channelId,s,n),user:t});return{id:c.id,raw:c.raw}},async postDirectMessage(e,t){let r=await request(`conversations.open`,{users:e}),i=r.ok===!0?r.channel?.id:void 0;if(typeof i!=`string`||i.length===0)throw Error(`Slack conversations.open failed: ${r.error??`unknown_error`}`);let a=normalizePostInput(t),o=await postSlackMessage(buildPostMessageOptions(a,i,``,n));return{id:o.id,raw:o.raw}},async startTyping(t){if(e.channelId&&a)try{let n=t===void 0?``:truncateTypingStatus(t),r={channel_id:e.channelId,thread_ts:a,status:n};n.length>0&&(r.loading_messages=[n]);let i=await request(`assistant.threads.setStatus`,r);i.ok!==!0&&log.warn(`assistant.threads.setStatus returned not-ok`,{error:i.error})}catch(t){logError(log,`startTyping threw — swallowed`,t,{channelId:e.channelId})}},refresh(){return refreshMessages()},mentionUser(e){return`<@${e}>`}},l={channelId:e.channelId,get threadTs(){return a},teamId:e.teamId,request,uploadFiles};return threadWindowFetchers.set(c,fetchWindow),{thread:c,slack:l}}function normalizePostInput(e){return typeof e==`string`?{markdown:e}:isCardElement(e)?{card:e}:e}function buildPostMessageOptions(e,t,n,r){let i={...r,channel:t,threadTs:n||void 0,unfurlLinks:!1,unfurlMedia:!1};return`card`in e?(i.blocks=cardToBlocks(e.card),i.text=e.fallbackText??cardToFallbackText(e.card),i):`blocks`in e?(i.blocks=[...e.blocks],e.text!==void 0&&(i.text=e.text),i):`markdown`in e?(i.markdownText=e.markdown,i):(i.text=e.text,i)}function normalizeSlackApiBody(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:{}}function toSlackFileUpload(e,t){return{data:normalizeFileData(e.data),filename:e.filename,snippetType:t}}function normalizeFileData(e){if(e instanceof ArrayBuffer||typeof Blob<`u`&&e instanceof Blob)return e;if(ArrayBuffer.isView(e)){let t=e;return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}return e}function parseThreadMessage(e,t,n){let r=typeof e.text==`string`?e.text:``,i=resolveSlackInboundMrkdwn(r,e),a=typeof e.ts==`string`?e.ts:``,o=typeof e.thread_ts==`string`?e.thread_ts:t,s=typeof e.user==`string`?e.user:void 0,c=typeof e.bot_id==`string`?e.bot_id:void 0,l=typeof e.app_id==`string`?e.app_id:void 0,u=n.botUserId!==void 0&&s===n.botUserId||n.appId!==void 0&&l===n.appId;return{text:i,markdown:slackMrkdwnToGfm(i),user:s,botId:c,ts:a,threadTs:o,isMe:u,raw:e}}export{THREAD_REFRESH_LIMIT,buildSlackBinding,buildSlackWorkspaceHandle,callSlackApi,fetchSlackThreadWindow,resolveSlackBotToken,slackContinuationToken};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SlackThreadMessage } from "#public/channels/slack/api.js";
|
|
2
2
|
import type { SlackInboundContext } from "#public/channels/slack/inbound.js";
|
|
3
|
+
import type { SessionHistoryMessage } from "#shared/session-history.js";
|
|
3
4
|
/** Renders the triggering inbound Slack message as one attributed block. */
|
|
4
5
|
export declare function formatSlackInboundMessage(context: SlackInboundContext, message: {
|
|
5
6
|
readonly text: string;
|
|
@@ -10,3 +11,9 @@ export declare function formatSlackInboundMessage(context: SlackInboundContext,
|
|
|
10
11
|
* Returns `undefined` when there are no messages to add to the turn.
|
|
11
12
|
*/
|
|
12
13
|
export declare function formatSlackThreadContext(messages: readonly SlackThreadMessage[]): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Maps fetched thread replies to seeded session history. This app's own replies become assistant
|
|
16
|
+
* turns; everyone else stays an attributed user message. Slack shows rendered output, so assistant
|
|
17
|
+
* turns approximate what the model said.
|
|
18
|
+
*/
|
|
19
|
+
export declare function slackThreadSessionHistory(messages: readonly SlackThreadMessage[]): SessionHistoryMessage[];
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";function formatSlackModelMessage(e){return[`<slack_message>`,`sender_type: ${e.senderType}`,...e.senderId?[`sender_id: ${e.senderId}`]:[],...e.botUserId?[`bot_user_id: ${e.botUserId}`]:[],...e.isMentioned===void 0?[]:[`is_mentioned: ${e.isMentioned}`],...e.channelId?[`channel_id: ${e.channelId}`]:[],`thread_ts: ${e.threadTs}`,`message_ts: ${e.ts}`,...e.teamId?[`team_id: ${e.teamId}`]:[],`<content>`,e.content,`</content>`,`</slack_message>`].join(`
|
|
2
2
|
`)}function formatSlackInboundMessage(e,t){return formatSlackModelMessage({botUserId:e.botUserId,channelId:e.channelId,content:slackModelContent(t.text),isMentioned:e.isMentioned,senderId:e.userId||void 0,senderType:e.userId?`user`:`unknown`,teamId:e.teamId,threadTs:e.threadTs,ts:t.ts})}function slackModelContent(e){let t=[],n=`eve_slack_user_mention_`;for(;e.includes(n);)n+=`_`;let r=e.replace(/<@[A-Z0-9_]+(?:\|[^>\r\n]+)?>/giu,e=>{let r=`${n}${t.length}\uE001`;return t.push(e),r}),i=slackMrkdwnToGfm(r);for(let[e,r]of t.entries())i=i.replaceAll(`${n}${e}\uE001`,r);return i}function formatSlackThreadContext(e){if(e.length!==0)return[`<slack_thread_context>`,...e.map(e=>formatSlackModelMessage({content:e.markdown,senderId:e.user??e.botId,senderType:slackThreadSenderType(e),threadTs:e.threadTs,ts:e.ts})),`</slack_thread_context>`].join(`
|
|
3
|
-
`)}function slackThreadSenderType(e){return e.isMe?`agent`:e.botId?`bot`:e.user?`user`:`unknown`}export{formatSlackInboundMessage,formatSlackThreadContext};
|
|
3
|
+
`)}function slackThreadSenderType(e){return e.isMe?`agent`:e.botId?`bot`:e.user?`user`:`unknown`}function slackThreadSessionHistory(e){return e.flatMap(e=>slackThreadHistoryMessage(e)??[])}function slackThreadHistoryMessage(e){if(e.markdown.trim().length!==0)return e.isMe?{id:e.ts,role:`assistant`,text:e.markdown}:{id:e.ts,role:`user`,text:formatSlackModelMessage({content:e.markdown,senderId:e.user??e.botId,senderType:slackThreadSenderType(e),threadTs:e.threadTs,ts:e.ts})}}export{formatSlackInboundMessage,formatSlackThreadContext,slackThreadSessionHistory};
|
|
@@ -31,6 +31,11 @@ export type SlackCompletedReply = string | {
|
|
|
31
31
|
* This requires the `files:write` bot scope. In a session without a
|
|
32
32
|
* thread yet, the note posts first and anchors the thread.
|
|
33
33
|
*
|
|
34
|
+
* Before any of that, while eve has not yet posted in the thread, it checks
|
|
35
|
+
* that the bound thread's root still exists. Slack posts a reply to a
|
|
36
|
+
* deleted root as a top-level channel message, so eve drops the reply
|
|
37
|
+
* instead of posting it unthreaded.
|
|
38
|
+
*
|
|
34
39
|
* Any other error, and a failed upload, is thrown so your renderer sees it.
|
|
35
40
|
* eve logs each fallback it takes with Slack's error code, never the reply.
|
|
36
41
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{SLACK_MARKDOWN_TEXT_MAX_LENGTH,truncateMessageText}from"#public/channels/slack/limits.js";const log=createLogger(`slack.reply`),LONG_RESPONSE_NOTICE=`Here's a snippet with the full response.`,FALLBACK_LOG=`Slack refused the final reply; delivering it with a fallback`,REFUSED_PAYLOAD_ERRORS=new Set([`invalid_blocks`,`invalid_blocks_format`,`msg_blocks_too_long`,`msg_blocks_too_many`,`msg_too_long`]);async function postCompletedSlackReply(e,t){await deliverCompletedSlackReply(e,t,{})}async function deliverCompletedSlackReply(e,t,n){let{blocks:r,markdown:i,text:a}=typeof t==`string`?{markdown:t}:t
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{SLACK_MARKDOWN_TEXT_MAX_LENGTH,truncateMessageText}from"#public/channels/slack/limits.js";const log=createLogger(`slack.reply`),LONG_RESPONSE_NOTICE=`Here's a snippet with the full response.`,FALLBACK_LOG=`Slack refused the final reply; delivering it with a fallback`,REFUSED_PAYLOAD_ERRORS=new Set([`invalid_blocks`,`invalid_blocks_format`,`msg_blocks_too_long`,`msg_blocks_too_many`,`msg_too_long`]);async function postCompletedSlackReply(e,t){await deliverCompletedSlackReply(e,t,{})}async function deliverCompletedSlackReply(e,t,n){let{blocks:r,markdown:i,text:a}=typeof t==`string`?{markdown:t}:t;if(await threadRootIsGone(e)){log.warn(`Slack thread root was deleted; dropping the final reply`,n);return}let o;if(r!==void 0)try{await e.thread.post({blocks:r,text:a??truncateMessageText(i)});return}catch(e){if(o=refusedPayloadError(e),o===void 0)throw e}if(i.length<=SLACK_MARKDOWN_TEXT_MAX_LENGTH){o!==void 0&&log.warn(FALLBACK_LOG,{fallback:`inline`,slackError:o,...n});try{await e.thread.post(i);return}catch(e){if(o=refusedPayloadError(e),o===void 0)throw e}}o!==void 0&&log.warn(FALLBACK_LOG,{fallback:`snippet`,slackError:o,...n}),await uploadReplySnippet(e,i)}async function threadRootIsGone(e){let{channelId:t,threadTs:n}=e.slack;if(!t||!n||e.state?.postedInThread===!0)return!1;try{let r=await e.slack.request(`chat.getPermalink`,{channel:t,message_ts:n});return r.ok!==!0&&r.error===`message_not_found`}catch{return!1}}async function uploadReplySnippet(e,t){let n={data:new Blob([t],{type:`text/markdown`}),filename:`eve-response.md`,mimeType:`text/markdown`},r=e.slack.threadTs.length>0;if(!r&&(!(await e.thread.post(LONG_RESPONSE_NOTICE)).id||e.slack.threadTs.length===0))throw Error(`Slack did not return a thread timestamp for the long response notice.`);await e.slack.uploadFiles([n],{initialComment:r?LONG_RESPONSE_NOTICE:void 0,snippetType:`markdown`})}function refusedPayloadError(e){let t=e;for(let e=0;e<5&&typeof t==`object`&&t;e++){let e=t,n=e.response?.error??e.data?.error;if(typeof n==`string`&&REFUSED_PAYLOAD_ERRORS.has(n))return n;t=e.cause}}function withFinalReplyDelivery(e){return async(t,n,r)=>{if(t.finishReason===`tool-calls`||!t.message){await e?.(t,n,r);return}try{await e?.(t,n,r)}catch(e){await reportUndeliveredReply(n,t.turnId,e)}}}async function reportUndeliveredReply(e,t,n){let r=logError(log,`final reply was not delivered to Slack`,n,{turnId:t});e.state.undeliveredReplyErrorId=r;try{await e.thread.post(`I finished, but couldn't deliver my answer in Slack (error id \`${r}\`). Please ask me to resend it.`)}catch(e){logError(log,`undelivered reply notice failed`,e,{errorId:r,turnId:t})}}function withUndeliveredReplyNote(e,t){let n=e.undeliveredReplyErrorId;if(!n||t.message===void 0)return t;e.undeliveredReplyErrorId=null;let r=[`Your previous reply was not delivered to Slack (error id ${n}).`,`The user has not seen it. Do not assume they read it; send the answer again if they ask for it.`].join(` `);return{...t,context:[r,...t.context??[]]}}export{deliverCompletedSlackReply,postCompletedSlackReply,withFinalReplyDelivery,withUndeliveredReplyNote};
|
|
@@ -5,6 +5,7 @@ import type { SessionAuthContext, TurnPolicy } from "#channel/types.js";
|
|
|
5
5
|
import type { VercelConnectMetadata } from "#shared/vercel-connect-metadata.js";
|
|
6
6
|
import type { CardElement } from "#compiled/chat/index.js";
|
|
7
7
|
import type { SessionContext } from "#public/definitions/callback-context.js";
|
|
8
|
+
import type { SessionHistoryMessage } from "#shared/session-history.js";
|
|
8
9
|
import type { ChannelContinuationOps } from "#public/definitions/channel.js";
|
|
9
10
|
import type { UnstampedMessageStreamEvent } from "#protocol/message.js";
|
|
10
11
|
import type { InputRequest, InputResponse, StrictInputResponses, ValidatedInputResponse } from "#shared/input.js";
|
|
@@ -166,6 +167,12 @@ export interface SlackChannelState {
|
|
|
166
167
|
* message tells the model the reply was not seen, then clears it.
|
|
167
168
|
*/
|
|
168
169
|
undeliveredReplyErrorId?: string | null;
|
|
170
|
+
/**
|
|
171
|
+
* Whether eve has posted in this thread. Once it has, deleting the root
|
|
172
|
+
* leaves a tombstone that keeps the thread, so the final reply skips the
|
|
173
|
+
* deleted-root check.
|
|
174
|
+
*/
|
|
175
|
+
postedInThread?: boolean;
|
|
169
176
|
}
|
|
170
177
|
/**
|
|
171
178
|
* Per-session metadata attached to tracing spans, projected by the
|
|
@@ -241,6 +248,8 @@ export interface SlackInitialMessage {
|
|
|
241
248
|
*/
|
|
242
249
|
export interface SlackEventSendOptions {
|
|
243
250
|
readonly auth: SessionAuthContext | null;
|
|
251
|
+
/** Prior conversation added as user and assistant turns before the message. */
|
|
252
|
+
readonly history?: readonly SessionHistoryMessage[];
|
|
244
253
|
readonly target: SlackReceiveTarget;
|
|
245
254
|
/** Overrides the workflow run title without changing the message sent to the model. */
|
|
246
255
|
readonly title?: string;
|
|
@@ -440,12 +449,15 @@ export type SlackInputResponseResult = {
|
|
|
440
449
|
* Result of an `onAppMention` or `onDirectMessage` callback. Return an
|
|
441
450
|
* object (auth may be `null`) to dispatch a turn, or `null` to drop the
|
|
442
451
|
* inbound message. `context` strings are appended as user messages to
|
|
443
|
-
* session history before the delivery message. `
|
|
444
|
-
*
|
|
452
|
+
* session history before the delivery message. `history` entries are added as
|
|
453
|
+
* user and assistant turns before it; when present, `threadContext` keeps its
|
|
454
|
+
* transcript and never seeds. `title` overrides the workflow run title without
|
|
455
|
+
* changing the message sent to the model.
|
|
445
456
|
*/
|
|
446
457
|
export type SlackMentionResult = {
|
|
447
458
|
readonly auth: SessionAuthContext | null;
|
|
448
459
|
readonly context?: readonly string[];
|
|
460
|
+
readonly history?: readonly SessionHistoryMessage[];
|
|
449
461
|
readonly title?: string;
|
|
450
462
|
} | null;
|
|
451
463
|
export type SlackMentionResultOrPromise = SlackMentionResult | Promise<SlackMentionResult>;
|
|
@@ -498,7 +510,9 @@ export interface SlackChannelConfig {
|
|
|
498
510
|
/**
|
|
499
511
|
* Adds earlier replies from the current Slack thread to each triggering
|
|
500
512
|
* turn. Messages are rendered with their Slack sender ids attached so a
|
|
501
|
-
* multi-user transcript retains unambiguous speaker attribution.
|
|
513
|
+
* multi-user transcript retains unambiguous speaker attribution. When the
|
|
514
|
+
* message starts the thread's session, the same replies are added as
|
|
515
|
+
* history instead: this app's replies become assistant turns. Omit this
|
|
502
516
|
* option to avoid fetching thread history.
|
|
503
517
|
*/
|
|
504
518
|
readonly threadContext?: LoadThreadContextMessagesOptions;
|
|
@@ -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{defaultDeliverResult}from"#channel/adapter.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{resolveSlackTransportOptions}from"#public/channels/slack/transport.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{buildSlackBinding,buildSlackWorkspaceHandle,fetchSlackThreadWindow,slackContinuationToken}from"#public/channels/slack/api.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackEventInstallationTeamId,slackEventReceivingBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{defaultInputRequestedHandler}from"#public/channels/slack/approval-cards.js";import{withFinalReplyDelivery,withUndeliveredReplyNote}from"#public/channels/slack/reply-delivery.js";import{defaultEvents,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}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(e.channelId,n))}});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.onInputResponse??defaultOnInputResponse,a=composeSlackRenderers(e.renderers??[],{events:{...defaultEvents,"input.requested":defaultInputRequestedHandler(e.approvalChannel)},received:defaultReceived,taskCard:renderDefaultSlackTaskCard}),o=a.events[`turn.started`],s=withTaskCards({...a.events,"message.completed":withFinalReplyDelivery(a.events[`message.completed`]),async"turn.started"(e,t,n){let r=slackUserIdFromAuthContext(n.session.auth.current);r!==void 0&&(t.state.triggeringUserId=r),await o?.(e,t,n)}},a.taskCard),c=new Set,l=defineChannel({kindHint:`slack`,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),defaultDeliverResult(withUndeliveredReplyNote(t.state,e))},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,onInputResponse: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 defaultOnInputResponse(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(r,c)).send(e.message,{auth:e.auth,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.onInputResponse===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(e.message.channelId,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(t.channelId,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(t.channelId,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,target:i,title:a})=>receiveOnSlack({auth:r,message:t,target:i,title:a},{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=formatSlackThreadContext(r),a=await collectInboundFileParts({botUserId:e.botUserId,fetchThreadWindow:(e,t)=>fetchSlackThreadWindow(n,e,t),isMentioned:e.kind===`app_mention`,mention:t,thread:n,policy:e.uploadPolicy}),o={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},s=formatSlackInboundMessage(o,t),c=buildSlackTurnMessage(i===void 0?s:`${i}\n\n${s}`,a),l=e.result.context??[],u=e.isPrivateConversation?`Private message`:e.result.title??t.markdown,d=attachInputText(l.length===0?{auth:e.result.auth,title:u}:{auth:e.result.auth,context:l,title:u},e.resolvesTypedInput&&a.length===0?slackTypedText(t.text,e.botUserId):void 0);await e.sessionOperations.send(c,d)}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{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{defaultDeliverResult}from"#channel/adapter.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{resolveSlackTransportOptions}from"#public/channels/slack/transport.js";import{buildSlackAuthContext,slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{buildSlackBinding,buildSlackWorkspaceHandle,fetchSlackThreadWindow,slackContinuationToken}from"#public/channels/slack/api.js";import{parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackEventInstallationTeamId,slackEventReceivingBotUserId,slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{defaultInputRequestedHandler}from"#public/channels/slack/approval-cards.js";import{withFinalReplyDelivery,withUndeliveredReplyNote}from"#public/channels/slack/reply-delivery.js";import{defaultEvents,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(e.channelId,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.onInputResponse??defaultOnInputResponse,a=composeSlackRenderers(e.renderers??[],{events:{...defaultEvents,"input.requested":defaultInputRequestedHandler(e.approvalChannel)},received:defaultReceived,taskCard:renderDefaultSlackTaskCard}),o=a.events[`turn.started`],s=withTaskCards({...a.events,"message.completed":withFinalReplyDelivery(a.events[`message.completed`]),async"turn.started"(e,t,n){let r=slackUserIdFromAuthContext(n.session.auth.current);r!==void 0&&(t.state.triggeringUserId=r),await o?.(e,t,n)}},a.taskCard),c=new Set,l=defineChannel({kindHint:`slack`,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),defaultDeliverResult(withUndeliveredReplyNote(t.state,e))},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,onInputResponse: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 defaultOnInputResponse(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(r,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.onInputResponse===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(e.message.channelId,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(t.channelId,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(t.channelId,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=attachInputText({auth:e.result.auth,title:f,...d.length>0&&{context:d},...o!==void 0&&{history:o}},e.resolvesTypedInput&&s.length===0?slackTypedText(t.text,e.botUserId):void 0);await e.sessionOperations.send(u,p)}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,3 +1,4 @@
|
|
|
1
|
+
import type { SessionHistoryMessage } from "#shared/session-history.js";
|
|
1
2
|
import type { TeamsInstrumentationMetadata } from "#public/channels/teams/index.js";
|
|
2
3
|
import type { SessionAuthContext, TurnPolicy } from "#channel/types.js";
|
|
3
4
|
import type { SessionContext } from "#public/definitions/callback-context.js";
|
|
@@ -91,6 +92,8 @@ export interface TeamsReceiveTarget {
|
|
|
91
92
|
export type TeamsInboundResult = {
|
|
92
93
|
readonly auth: SessionAuthContext | null;
|
|
93
94
|
readonly context?: readonly string[];
|
|
95
|
+
/** Prior conversation added as user and assistant turns before this message. */
|
|
96
|
+
readonly history?: readonly SessionHistoryMessage[];
|
|
94
97
|
/** Overrides the workflow run title without changing the message sent to the model. */
|
|
95
98
|
readonly title?: string;
|
|
96
99
|
} | null;
|