eve 0.81.0 → 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 +37 -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 +2 -2
- package/dist/src/compiled/@eve/catalog/index.js +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/create-request.d.ts +2 -1
- package/dist/src/eve-channel/index.js +1 -1
- package/dist/src/eve-channel/request.d.ts +2 -1
- package/dist/src/eve-channel/request.js +1 -2
- package/dist/src/eve-channel/support.d.ts +11 -1
- package/dist/src/eve-channel/support.js +2 -1
- package/dist/src/eve-channel/types.d.ts +25 -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/hook-ownership.d.ts +8 -0
- package/dist/src/execution/hook-ownership.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 +20 -8
- package/dist/src/execution/session/entry-input.js +1 -1
- package/dist/src/execution/session/entry.js +1 -1
- package/dist/src/execution/session/finalization.js +1 -1
- package/dist/src/execution/session/handoff-steps.d.ts +75 -13
- package/dist/src/execution/session/handoff-steps.js +1 -1
- package/dist/src/execution/session/handoff.d.ts +73 -34
- 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/run-watch.d.ts +22 -0
- package/dist/src/execution/session/run-watch.js +1 -0
- 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/address.d.ts +4 -1
- package/dist/src/execution/session-inbox/end-stranded-session.js +1 -1
- package/dist/src/execution/session-inbox/hook-release.d.ts +9 -2
- package/dist/src/execution/session-inbox/hook-release.js +1 -1
- package/dist/src/execution/session-inbox/inbox.d.ts +66 -6
- package/dist/src/execution/session-inbox/inbox.js +1 -1
- package/dist/src/execution/session-inbox/protocol.js +1 -1
- package/dist/src/execution/session-inbox/resume.d.ts +4 -3
- 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 +11 -12
- 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/call.js +1 -1
- 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/instrumentation/content.d.ts +1 -1
- package/dist/src/instrumentation/content.js +1 -1
- package/dist/src/instrumentation/lifecycle.d.ts +4 -2
- 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/connection.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/mcp/skills.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-client.js +1 -1
- 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/messageToUserContent.d.ts +2 -1
- package/dist/src/public/channels/chat-sdk/messageToUserContent.js +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/attachments.d.ts +5 -2
- package/dist/src/public/channels/photon/attachments.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 +14 -1
- 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/gateway-routing.d.ts +20 -0
- package/dist/src/shared/gateway-routing.js +1 -0
- package/dist/src/shared/session-history.d.ts +22 -0
- package/dist/src/shared/session-history.js +1 -0
- package/dist/src/shared/skill-package.js +1 -1
- 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/agent-otel-provider.js +1 -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/gateway.d.ts +5 -0
- package/dist/src/tracing/eve/gateway.js +1 -1
- package/dist/src/tracing/eve/invoke-tool-span.js +1 -1
- package/dist/src/tracing/eve/operation-input.d.ts +0 -12
- 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/policy.d.ts +1 -1
- package/dist/src/tracing/eve/policy.js +1 -1
- package/dist/src/tracing/eve/session.d.ts +5 -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/README.md +1 -0
- package/docs/agent-config.md +108 -65
- package/docs/channels/chat-sdk.mdx +10 -3
- package/docs/channels/custom.mdx +38 -6
- package/docs/channels/eve.mdx +73 -0
- package/docs/channels/linq.mdx +51 -0
- package/docs/channels/photon.mdx +68 -2
- package/docs/channels/slack.mdx +6 -2
- package/docs/concepts/built-in-tools.md +1 -1
- package/docs/concepts/execution-model-and-durability.mdx +27 -11
- 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/guides/instrumentation/otel.mdx +20 -0
- package/docs/memory/overview.mdx +2 -1
- package/docs/patterns/dynamic-scheduling.md +3 -1
- package/docs/patterns/meta.json +1 -0
- package/docs/patterns/voice-notes.md +67 -0
- package/docs/reference/cli.md +14 -7
- package/docs/sandbox/index.mdx +2 -0
- 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
|
@@ -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};
|
|
@@ -2,6 +2,8 @@ import type { Attachment, Message } from "#compiled/chat/index.js";
|
|
|
2
2
|
/** The adapter that owns the spectrum-ts app eve downloads attachments through. */
|
|
3
3
|
interface PhotonAdapter {
|
|
4
4
|
readonly app: unknown;
|
|
5
|
+
/** Asks Photon for the message by id; webhook deliveries are never cached, so this refetches. */
|
|
6
|
+
fetchMessage(threadId: string, messageId: string): Promise<Message | null>;
|
|
5
7
|
rehydrateAttachment?(attachment: Attachment): Attachment;
|
|
6
8
|
}
|
|
7
9
|
/**
|
|
@@ -9,9 +11,10 @@ interface PhotonAdapter {
|
|
|
9
11
|
* webhook lists an attachment's name and type but never its bytes, so this
|
|
10
12
|
* records each attachment's id in its `fetchMetadata` and gives it a
|
|
11
13
|
* `fetchData`; the channel downloads it later, in the step, through
|
|
12
|
-
* {@link addPhotonAttachmentRehydration}.
|
|
14
|
+
* {@link addPhotonAttachmentRehydration}. The webhook omits the id for some
|
|
15
|
+
* files, such as voice notes, so those come from the message refetched by id.
|
|
13
16
|
*/
|
|
14
|
-
export declare function addPhotonAttachmentDownloads(message: Message, adapter: PhotonAdapter): void
|
|
17
|
+
export declare function addPhotonAttachmentDownloads(message: Message, adapter: PhotonAdapter): Promise<void>;
|
|
15
18
|
/**
|
|
16
19
|
* Gives the adapter a `rehydrateAttachment`, which the iMessage adapter lacks,
|
|
17
20
|
* so the channel can rebuild an attachment's download from its `fetchMetadata`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function addPhotonAttachmentDownloads(e,t){let n=e.attachments??[];if(n.length===0)return;let r=e.raw,i=contentAttachmentIds(r.content),a=spacePhone(r.space);n.forEach((e,n)=>{let r=i[n];e.fetchData===void 0&&r!==void 0&&(e.fetchMetadata=a===void 0?{id:r}:{id:r,phone:a},e.fetchData=()=>downloadAttachment(t.app,r,a))})}function addPhotonAttachmentRehydration(e){e.rehydrateAttachment=t=>{let{id:n,phone:r}=t.fetchMetadata??{};return n===void 0?t:{...t,fetchData:()=>downloadAttachment(e.app,n,r)}}}async function downloadAttachment(e,t,n){let r=e?.__internal?.platforms.get(`iMessage`),i=r?.definition.actions?.getAttachment;if(r===void 0||i===void 0)throw Error(`The iMessage adapter has no Spectrum app to download attachments with.`);let a=await i({client:r.client,config:r.config,store:r.store},t,n);if(a===void 0)throw Error(`iMessage attachment ${t} was not found.`);return await a.read()}function contentAttachmentIds(e){return e===void 0?[]:e.type===`attachment`||e.type===`voice`?[typeof e.id==`string`&&e.id.length>0?e.id:void 0]:e.type===`reply`?contentAttachmentIds(e.content):e.type===`group`?(e.items??[]).flatMap(e=>contentAttachmentIds(e.content)):[]}function spacePhone(e){if(typeof e!=`object`||!e)return;let t=e.phone;return typeof t==`string`?t:void 0}export{addPhotonAttachmentDownloads,addPhotonAttachmentRehydration};
|
|
1
|
+
async function addPhotonAttachmentDownloads(e,t){let n=e.attachments??[];if(n.length===0)return;let r=e.raw,i=contentAttachmentIds(r.content);if(n.some((e,t)=>i[t]===void 0)){let r=await refetchedAttachmentIds(t,e);n.forEach((e,t)=>{i[t]??=r[t]})}let a=spacePhone(r.space);n.forEach((e,n)=>{let r=i[n];e.fetchData===void 0&&r!==void 0&&(e.fetchMetadata=a===void 0?{id:r}:{id:r,phone:a},e.fetchData=()=>downloadAttachment(t.app,r,a))})}function addPhotonAttachmentRehydration(e){e.rehydrateAttachment=t=>{let{id:n,phone:r}=t.fetchMetadata??{};return n===void 0?t:{...t,fetchData:()=>downloadAttachment(e.app,n,r)}}}async function refetchedAttachmentIds(e,t){try{let n=(await e.fetchMessage(t.threadId,t.id))?.raw;return contentAttachmentIds(n?.content)}catch{return[]}}async function downloadAttachment(e,t,n){let r=e?.__internal?.platforms.get(`iMessage`),i=r?.definition.actions?.getAttachment;if(r===void 0||i===void 0)throw Error(`The iMessage adapter has no Spectrum app to download attachments with.`);let a=await i({client:r.client,config:r.config,store:r.store},t,n);if(a===void 0)throw Error(`iMessage attachment ${t} was not found.`);return await a.read()}function contentAttachmentIds(e){return e===void 0?[]:e.type===`attachment`||e.type===`voice`?[typeof e.id==`string`&&e.id.length>0?e.id:void 0]:e.type===`reply`?contentAttachmentIds(e.content):e.type===`group`?(e.items??[]).flatMap(e=>contentAttachmentIds(e.content)):[]}function spacePhone(e){if(typeof e!=`object`||!e)return;let t=e.phone;return typeof t==`string`?t:void 0}export{addPhotonAttachmentDownloads,addPhotonAttachmentRehydration};
|
|
@@ -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};
|
|
@@ -203,7 +203,9 @@ export interface SlackThread {
|
|
|
203
203
|
post(message: string | CardElement | SlackPostInput): Promise<SlackPostedMessage>;
|
|
204
204
|
/**
|
|
205
205
|
* Post an ephemeral reply (Slack's `chat.postEphemeral`) visible only
|
|
206
|
-
* to one user in this thread.
|
|
206
|
+
* to one user in this thread. Slack hides threaded ephemerals until the
|
|
207
|
+
* thread has a reply, so on a fresh top-level message it posts to the
|
|
208
|
+
* channel instead. Accepts the same bare forms and
|
|
207
209
|
* {@link SlackPostInput} variants as {@link post}. The `files` field is
|
|
208
210
|
* ignored: Slack does not support file uploads on ephemeral messages.
|
|
209
211
|
*/
|
|
@@ -319,6 +321,11 @@ export declare function fetchSlackThreadWindow(thread: SlackThread, latest: stri
|
|
|
319
321
|
* live `threadTs` and firing `onThreadTsChanged` so the caller can
|
|
320
322
|
* persist the anchor. Ephemerals and upload-only file posts do not
|
|
321
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.
|
|
322
329
|
*/
|
|
323
330
|
export declare function buildSlackBinding(input: {
|
|
324
331
|
/** Slack app id used to identify this app's fetched thread replies. */
|
|
@@ -329,10 +336,16 @@ export declare function buildSlackBinding(input: {
|
|
|
329
336
|
readonly botUserId?: string;
|
|
330
337
|
readonly channelId: string;
|
|
331
338
|
readonly threadTs: string;
|
|
339
|
+
/**
|
|
340
|
+
* Whether `threadTs` already has replies; Slack hides threaded
|
|
341
|
+
* ephemerals until it does. Defaults to true when `threadTs` is set.
|
|
342
|
+
*/
|
|
343
|
+
readonly threadHasReplies?: boolean;
|
|
332
344
|
/** Workspace whose app installation supplies the bot token. */
|
|
333
345
|
readonly installationTeamId?: string;
|
|
334
346
|
/** Actor/content workspace exposed on the public handle. */
|
|
335
347
|
readonly teamId: string | undefined;
|
|
336
348
|
readonly onThreadTsChanged?: (ts: string) => void;
|
|
349
|
+
readonly onPosted?: () => void;
|
|
337
350
|
}): SlackBinding;
|
|
338
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;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,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;
|