eve 0.84.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/dist/src/channel/channel-operations.d.ts +2 -18
- package/dist/src/channel/compiled-channel.d.ts +3 -7
- package/dist/src/channel/cross-channel-receive.d.ts +13 -11
- package/dist/src/channel/cross-channel-receive.js +1 -1
- package/dist/src/channel/delivery.js +1 -1
- package/dist/src/channel/schedule.js +1 -1
- package/dist/src/channel/session.d.ts +26 -4
- package/dist/src/channel/session.js +1 -1
- package/dist/src/channel/types.d.ts +2 -0
- package/dist/src/chunks/{use-eve-agent-BbbguIdB.js → use-eve-agent-B2jCfcnG.js} +29 -12
- package/dist/src/chunks/{use-eve-agent-DWjyzplb.js → use-eve-agent-DCLFi26P.js} +29 -12
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/client/client.js +1 -1
- package/dist/src/client/delivery.d.ts +2 -0
- package/dist/src/client/delivery.js +1 -1
- package/dist/src/client/open-stream.js +1 -1
- package/dist/src/client/optimistic-message-submissions.js +1 -1
- package/dist/src/client/session-store.js +1 -1
- package/dist/src/client/session.js +1 -1
- package/dist/src/client/sessions.d.ts +1 -0
- package/dist/src/client/sessions.js +1 -1
- package/dist/src/client/types.d.ts +7 -4
- package/dist/src/compiled/.vendor-stamp.json +2 -2
- package/dist/src/compiled/@chat-adapter/slack/index.js +13 -13
- package/dist/src/compiled/@vercel/sandbox/api-client/api-client.d.ts +75 -0
- package/dist/src/compiled/@vercel/sandbox/api-client/validators.d.ts +4 -0
- package/dist/src/compiled/@vercel/sandbox/command.d.ts +78 -0
- package/dist/src/compiled/@vercel/sandbox/drive.d.ts +41 -1
- package/dist/src/compiled/@vercel/sandbox/index.js +13 -13
- package/dist/src/compiled/@vercel/sandbox/network-policy.d.ts +35 -4
- package/dist/src/compiled/@vercel/sandbox/sandbox.d.ts +18 -4
- package/dist/src/compiled/@vercel/sandbox/session.d.ts +29 -3
- package/dist/src/compiled/@vercel/sandbox/utils/pipe-stdin.d.ts +19 -0
- package/dist/src/compiled/_chunks/client/{core-CspsxXR-.js → core-BsvaaMUJ.js} +1 -1
- package/dist/src/compiled/_chunks/node/{auth-Dztlx9pE.js → auth-v5pS3mLo.js} +1 -1
- package/dist/src/compiled/_chunks/node/is-stream-DsuQX_bd.js +1 -0
- package/dist/src/compiled/_chunks/node/token-DHGH-jVc.js +1 -0
- package/dist/src/compiled/_chunks/node/token-util-BH2pw9Qm.js +25 -0
- package/dist/src/compiled/_chunks/node/token-util-CcvxNv76.js +1 -0
- package/dist/src/compiled/_chunks/node/version-C2PfNyEv.js +1 -0
- package/dist/src/compiled/zod/index.js +1 -1
- package/dist/src/compiled/zod/v4/core.js +1 -1
- package/dist/src/compiled/zod-validation-error/index.js +1 -1
- package/dist/src/compiler/artifacts.d.ts +1 -0
- package/dist/src/compiler/artifacts.js +1 -1
- package/dist/src/compiler/manifest.d.ts +2 -2
- package/dist/src/compiler/manifest.js +1 -1
- package/dist/src/compiler/model-catalog.d.ts +8 -18
- package/dist/src/compiler/model-catalog.js +1 -1
- package/dist/src/compiler/normalize-agent-config.js +1 -1
- package/dist/src/compiler/normalize-manifest-types.d.ts +3 -0
- package/dist/src/compiler/normalize-manifest.js +1 -1
- package/dist/src/context/keys.d.ts +7 -8
- package/dist/src/context/keys.js +1 -1
- package/dist/src/eve-channel/index.js +1 -1
- package/dist/src/eve-channel/request.d.ts +1 -0
- package/dist/src/eve-channel/request.js +1 -1
- package/dist/src/eve-channel/support.js +1 -1
- package/dist/src/execution/agent-sessions/context.d.ts +5 -1
- package/dist/src/execution/agent-sessions/context.js +1 -1
- package/dist/src/execution/agent-sessions/steps.js +1 -1
- package/dist/src/execution/dynamic-workflow/tool.js +1 -1
- package/dist/src/execution/legacy-session/snapshot.d.ts +1 -2
- package/dist/src/execution/legacy-session/snapshot.js +1 -1
- package/dist/src/execution/proxied-deliver-step.js +1 -1
- package/dist/src/execution/public-destination.d.ts +3 -0
- package/dist/src/execution/public-destination.js +1 -1
- package/dist/src/execution/publish-session-events.js +1 -1
- package/dist/src/execution/sandbox/bindings/docker-cli.d.ts +2 -0
- package/dist/src/execution/sandbox/bindings/docker-cli.js +1 -1
- package/dist/src/execution/sandbox/bindings/docker-connection.js +1 -1
- package/dist/src/execution/sandbox/bindings/just-bash-runtime.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
- package/dist/src/execution/sandbox/multiplexed-command.d.ts +11 -1
- package/dist/src/execution/sandbox/multiplexed-command.js +1 -1
- package/dist/src/execution/sandbox/session.js +1 -1
- package/dist/src/execution/session/checkpoint-migrations.d.ts +16 -5
- package/dist/src/execution/session/checkpoint-migrations.js +1 -1
- package/dist/src/execution/session/checkpoint-refusal.d.ts +4 -0
- package/dist/src/execution/session/checkpoint-refusal.js +1 -0
- package/dist/src/execution/session/entry.js +1 -1
- package/dist/src/execution/session/handoff-steps.d.ts +7 -1
- package/dist/src/execution/session/handoff-steps.js +1 -1
- package/dist/src/execution/session/imported-conversation.d.ts +2 -0
- package/dist/src/execution/session/imported-conversation.js +1 -0
- package/dist/src/execution/session/interrupted-step.js +1 -1
- package/dist/src/execution/session/pre-v27-checkpoint.d.ts +36 -0
- package/dist/src/execution/session/pre-v27-checkpoint.js +1 -0
- package/dist/src/execution/session/program.d.ts +2 -0
- package/dist/src/execution/session/program.js +1 -1
- package/dist/src/execution/session/turn-step.js +1 -1
- package/dist/src/execution/session-callback-request.js +1 -1
- package/dist/src/execution/session-inbox/address.d.ts +1 -0
- package/dist/src/execution/session-inbox/handle.d.ts +7 -0
- package/dist/src/execution/session-inbox/handle.js +1 -0
- package/dist/src/execution/session-inbox/owner.d.ts +4 -4
- package/dist/src/execution/session-inbox/owner.js +1 -1
- package/dist/src/execution/session-inbox/resume.d.ts +1 -0
- package/dist/src/execution/session-inbox/resume.js +1 -1
- package/dist/src/execution/tool-auth.js +1 -1
- package/dist/src/execution/tools/agent-router-workflow.js +1 -1
- package/dist/src/execution/tools/channel-to.d.ts +12 -0
- package/dist/src/execution/tools/channel-to.js +1 -0
- package/dist/src/execution/tools/workflow/body.d.ts +3 -0
- package/dist/src/execution/tools/workflow/body.js +1 -1
- package/dist/src/execution/tools/workflow/step-context.d.ts +5 -0
- package/dist/src/execution/tools/workflow/step-execution.js +1 -1
- package/dist/src/execution/tools/workflow/step.js +1 -1
- package/dist/src/execution/workflow-callback-url.d.ts +2 -3
- package/dist/src/execution/workflow-callback-url.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/harness/compaction/step.js +1 -1
- package/dist/src/harness/model-call/call.d.ts +3 -1
- package/dist/src/harness/model-call/call.js +1 -1
- package/dist/src/harness/model-call/run.js +1 -1
- package/dist/src/harness/semantic-errors/rules/gateway.js +1 -1
- package/dist/src/harness/session-machine/transitions.js +1 -1
- package/dist/src/internal/application/compiled-artifacts.d.ts +2 -0
- package/dist/src/internal/application/compiled-artifacts.js +2 -2
- package/dist/src/internal/application/optional-package-install.js +1 -1
- package/dist/src/internal/application/stamped-package-version.js +1 -1
- package/dist/src/internal/attachments/limited-read.d.ts +2 -0
- package/dist/src/internal/attachments/limited-read.js +1 -1
- package/dist/src/internal/attachments/url-refs.js +1 -1
- package/dist/src/internal/capability-error.d.ts +3 -0
- package/dist/src/internal/capability-error.js +1 -0
- package/dist/src/internal/model-catalog.d.ts +39 -16
- package/dist/src/internal/model-catalog.js +1 -1
- package/dist/src/internal/nitro/host/dev-build-host.js +1 -1
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/protocol/delivery.d.ts +1 -0
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +1 -1
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/github/githubChannel.js +1 -1
- package/dist/src/public/channels/linear/linearChannel.js +1 -1
- package/dist/src/public/channels/linq/inboundContent.js +1 -1
- package/dist/src/public/channels/slack/answers.d.ts +2 -3
- package/dist/src/public/channels/slack/answers.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +23 -3
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/app-manifest.d.ts +2 -0
- package/dist/src/public/channels/slack/app-manifest.js +1 -1
- package/dist/src/public/channels/slack/auth.d.ts +2 -1
- package/dist/src/public/channels/slack/auth.js +1 -1
- package/dist/src/public/channels/slack/code-channel.d.ts +42 -0
- package/dist/src/public/channels/slack/code-channel.js +1 -0
- package/dist/src/public/channels/slack/defaults.d.ts +7 -3
- package/dist/src/public/channels/slack/defaults.js +3 -3
- package/dist/src/public/channels/slack/hitl.d.ts +7 -17
- package/dist/src/public/channels/slack/hitl.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +4 -3
- package/dist/src/public/channels/slack/index.js +1 -1
- package/dist/src/public/channels/slack/interaction-cards.d.ts +0 -2
- package/dist/src/public/channels/slack/interaction-cards.js +1 -1
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/open-code-channel.d.ts +30 -0
- package/dist/src/public/channels/slack/open-code-channel.js +1 -0
- package/dist/src/public/channels/slack/private-approval-delivery.d.ts +16 -4
- package/dist/src/public/channels/slack/private-approval-delivery.js +1 -1
- package/dist/src/public/channels/slack/renderers.d.ts +58 -6
- package/dist/src/public/channels/slack/renderers.js +1 -1
- package/dist/src/public/channels/slack/reply-delivery.d.ts +4 -0
- package/dist/src/public/channels/slack/reply-delivery.js +1 -1
- package/dist/src/public/channels/slack/request-card-render.d.ts +45 -0
- package/dist/src/public/channels/slack/request-card-render.js +1 -0
- package/dist/src/public/channels/slack/request-cards.d.ts +35 -0
- package/dist/src/public/channels/slack/request-cards.js +3 -0
- package/dist/src/public/channels/slack/sign-in-cancel.d.ts +3 -5
- package/dist/src/public/channels/slack/sign-in-cancel.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +109 -22
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/slack/task-card.d.ts +3 -3
- package/dist/src/public/channels/slack/task-card.js +1 -1
- package/dist/src/public/channels/slack/thread-status.js +1 -1
- package/dist/src/public/channels/teams/api.js +1 -1
- package/dist/src/public/channels/teams/defaults.js +1 -1
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/teams/verify.d.ts +9 -5
- package/dist/src/public/channels/teams/verify.js +1 -1
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
- package/dist/src/public/nuxt/module.js +1 -1
- package/dist/src/public/schedules/subscription.d.ts +10 -1
- package/dist/src/reactions/kinds/model.js +1 -1
- package/dist/src/reactions/kinds/subagent.js +1 -1
- package/dist/src/runtime/agent/resolve-model.d.ts +3 -4
- package/dist/src/runtime/agent/resolve-model.js +1 -1
- package/dist/src/runtime/loaders/artifact-paths.d.ts +1 -0
- package/dist/src/runtime/loaders/artifact-paths.js +1 -1
- package/dist/src/runtime/loaders/bundled-artifacts.d.ts +2 -0
- package/dist/src/runtime/loaders/bundled-artifacts.js +1 -1
- package/dist/src/runtime/loaders/model-metadata.d.ts +4 -0
- package/dist/src/runtime/loaders/model-metadata.js +1 -0
- package/dist/src/runtime/resolve-agent.js +1 -1
- package/dist/src/runtime/sessions/compiled-agent-cache.d.ts +3 -0
- package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
- package/dist/src/runtime/subagents/dynamic-agent-config.d.ts +2 -2
- package/dist/src/runtime/subagents/dynamic-agent-config.js +1 -1
- package/dist/src/runtime/subagents/workflow.js +1 -1
- package/dist/src/sandbox/provider.d.ts +2 -0
- package/dist/src/setup/flows/model-source-change.js +1 -1
- package/dist/src/setup/index.d.ts +1 -1
- package/dist/src/setup/primitives/index.d.ts +1 -1
- package/dist/src/setup/primitives/pm/run.d.ts +0 -2
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/shared/agent-definition.d.ts +0 -1
- package/dist/src/shared/channel-definition.d.ts +4 -4
- package/dist/src/shared/sandbox-session.d.ts +4 -0
- package/dist/src/subagents/adapter.js +1 -1
- package/dist/src/subagents/parent-notification.js +1 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/tools/definition.d.ts +21 -0
- package/dist/src/tools/provided/ask-question-workflow.js +1 -1
- package/dist/src/tools/provided/sleep-workflow.js +1 -1
- package/dist/src/tools/workflow-definition.d.ts +4 -4
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/agent-config.md +5 -4
- package/docs/channels/custom.mdx +16 -3
- package/docs/channels/eve.mdx +2 -0
- package/docs/channels/linq.mdx +1 -1
- package/docs/channels/slack.mdx +185 -18
- package/docs/channels/teams.mdx +6 -2
- package/docs/concepts/default-harness.md +6 -0
- package/docs/concepts/upgrade-to-v27-events.md +2 -2
- package/docs/evals/judge.mdx +13 -0
- package/docs/experimental/dynamic-schedules.mdx +2 -2
- package/docs/guides/client/overview.mdx +24 -2
- package/docs/guides/remote-agents.md +8 -0
- package/docs/patterns/fork-conversations-into-new-threads.md +159 -0
- package/docs/patterns/meta.json +1 -0
- package/docs/reference/cli.md +1 -1
- package/docs/sandbox/custom-providers.mdx +1 -1
- package/docs/sandbox/index.mdx +1 -1
- package/docs/schedules.mdx +1 -1
- package/docs/tools/overview.mdx +34 -0
- package/docs/tools/workflows.mdx +51 -5
- package/package.json +2 -2
- package/dist/src/compiled/_chunks/node/token-Bedbhv5z.js +0 -1
- package/dist/src/compiled/_chunks/node/token-util-DpJNusVS.js +0 -1
- package/dist/src/compiled/_chunks/node/token-util-DwHQ2a3c.js +0 -1
- package/dist/src/compiled/_chunks/node/version-C__MqbLM.js +0 -1
- package/dist/src/public/channels/slack/approval-cards.d.ts +0 -25
- package/dist/src/public/channels/slack/approval-cards.js +0 -2
- package/dist/src/runtime/agent/model-catalog.d.ts +0 -10
- package/dist/src/runtime/agent/model-catalog.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createAttachSessionFn}from"#channel/session.js";import{createLogger,logError}from"#internal/logging.js";import{readTrustedDevelopmentClientAddress}from"#internal/nitro/dev-client-address.js";import{withVercelOidcProjectResolver}from"#channel/auth/vercel-oidc-project.js";import{createChannelDeliveryMetadata}from"#channel/delivery-metadata.js";import{createChannelOperations}from"#channel/channel-operations.js";import{getChannelInstrumentationKind}from"#channel/compiled-channel.js";import{createCrossChannelToFn,toCrossChannelTargets}from"#channel/cross-channel-receive.js";import{readVercelProjectLink}from"#internal/vercel/project-link.js";import{sessionCommandErrorResponse}from"#eve-channel/support.js";import{getWorld}from"#internal/workflow/runtime.js";import{attachAgentInfoRouteResponse,attachHomeRouteMetadata,attachRemoteAgentStreamHeadersResolver,attachRouteChannelName,attachRouteSessionCreator,attachSkillFileSource}from"#internal/nitro/routes/channel-route-context.js";import{context,trace}from"#compiled/@opentelemetry/api/index.js";import{DEVELOPMENT_WORKFLOW_SECRET_ENV}from"#internal/workflow/development-world-protocol.js";import{resolveNitroCompiledArtifactsSource}from"#internal/nitro/routes/runtime-artifacts.js";import{withLocalDevRequestScope}from"#runtime/local-dev-capability.js";import{createAgentDescriptionRouteArgs}from"#channel/agent-description.js";import{traceChannelRequest}from"#internal/nitro/routes/channel-request-instrumentation.js";import{resolveNitroChannelRuntimeBundle}from"#internal/nitro/routes/runtime-stack.js";import{createRouteInvokeTool}from"#internal/nitro/routes/route-invoke-tool.js";const log=createLogger(`channel.dispatch`);async function dispatchChannelRequest(e,t,n){return await traceChannelRequest({request:e.req,routeKey:t},async r=>{let i=r?.reference??trace.getSpan(context.active())?.spanContext(),a=await resolveNitroChannelRuntimeBundle(n),o=a.channels.find(e=>`${e.method.toUpperCase()} ${e.urlPath}`===t);if(o===void 0)return Response.json({error:`No matching channel for this request.`,ok:!1},{status:404});r?.channel({channelName:o.name});let s=getChannelInstrumentationKind(o.definition)??o.adapter?.kind;s!==void 0&&r?.channel({channelKind:s});let c=await buildRouteArgs(e,a,o.name,s??`channel`,n,i),l;try{l=await withDevelopmentVercelOidcContext(n,e.req,async()=>{if(o.handler)return await o.handler(e.req,c.args);let t={waitUntil:c.args.waitUntil,params:c.args.params,requestIp:c.args.requestIp};return await o.fetch(e.req,t)})}catch(n){let r=sessionCommandErrorResponse(n);if(r!==void 0)return flushBackgroundTasks(e,c.backgroundTasks,t,o.name),r;let i=logError(log,`channel handler threw`,n,{routeKey:t,channel:o.name});return flushBackgroundTasks(e,c.backgroundTasks,t,o.name),Response.json({error:`Channel handler failed.`,errorId:i,ok:!1},{status:500})}return flushBackgroundTasks(e,c.backgroundTasks,t,o.name),l})}async function dispatchChannelWebSocketRequest(e,t,n){let r=await resolveNitroChannelRuntimeBundle(n),i=r.channels.find(e=>`${e.method.toUpperCase()} ${e.urlPath}`===t);if(i===void 0||i.websocket===void 0)return rejectWebSocketUpgrade({error:`No matching websocket channel for this request.`,ok:!1},404);let a=i.websocket,o=getChannelInstrumentationKind(i.definition)??i.adapter?.kind??`channel`,s=await buildRouteArgs(e,r,i.name,o,n,void 0);try{let r=await withDevelopmentVercelOidcContext(n,e.req,async()=>await a(e.req,s.args));return flushBackgroundTasks(e,s.backgroundTasks,t,i.name),r}catch(n){let r=logError(log,`channel websocket handler threw`,n,{routeKey:t,channel:i.name});return flushBackgroundTasks(e,s.backgroundTasks,t,i.name),rejectWebSocketUpgrade({error:`Channel websocket handler failed.`,errorId:r,ok:!1},500)}}async function withDevelopmentVercelOidcContext(e,t,n){return e.kind===`development`?await withLocalDevRequestScope(t,async()=>await withVercelOidcProjectResolver({request:t,resolveCurrentProject:async()=>{let t=await readVercelProjectLink(e.appRoot);return t===void 0?void 0:{environment:`development`,projectId:t.projectId}}},n)):await n()}async function buildRouteArgs(e,t,n,r,i,a){let o=readVercelRequestId(e.req.headers),s=extractRequestIp(e,i),c=[],l=e.context.params??{},u={};for(let[e,t]of Object.entries(l))u[e]=decodeURIComponent(t);let waitUntil=e=>{c.push(e)},d=t.channels.find(e=>e.name===n),f=d?.adapter??{kind:`channel`},p={acceptedDeploymentId:await resolveAcceptedDeploymentId(i),channelKind:r,channelName:n,requestId:o,requestTraceContext:a===void 0?void 0:{spanId:a.spanId,traceFlags:a.traceFlags,traceId:a.traceId}},m=createChannelOperations({adapter:f,channelName:n,metadata:p,runtime:t.runtime,turnPolicy:d?.turnPolicy}),h=createAttachSessionFn(t.runtime,{...p,turnPolicy:d?.turnPolicy}),g=createCrossChannelToFn(t.runtime,toCrossChannelTargets(t.channels)),_=createAgentDescriptionRouteArgs(()=>resolveNitroCompiledArtifactsSource(i)),v=attachRouteSessionCreator(attachHomeRouteMetadata(attachRouteChannelName(attachAgentInfoRouteResponse({attachSession:h,...m,..._.args,invokeTool:createRouteInvokeTool({agentName:t.agentName,config:i,origin:{adapter:f,agentName:t.agentName,channelName:n},requestUrl:e.req.url}),params:u,requestIp:s,to:g,waitUntil},async()=>{let{handleAgentInfoRequest:e}=await import(`#internal/nitro/routes/info.js`);return await e(i)}),n),{agentName:t.agentName}),async e=>{let{deliveryId:r,...i}=e,a=createChannelDeliveryMetadata(p),s=r===void 0?a:{...a,deliveryId:r};return{...await t.runtime.createSession({...i,adapter:f,channelName:n,continuationToken:e.continuationToken===void 0?void 0:`${n}:${e.continuationToken}`,delivery:s,requestId:o}),deliveryId:s.deliveryId}});return attachSkillFileSource(v,_.skillFiles),t.resolveRemoteAgentStreamHeaders!==void 0&&attachRemoteAgentStreamHeadersResolver(v,t.resolveRemoteAgentStreamHeaders),{args:v,backgroundTasks:c}}async function resolveAcceptedDeploymentId(e){let t=process.env.VERCEL_DEPLOYMENT_ID?.trim();if(t!==void 0&&t.length>0)return validateDeploymentId(t);if(e.kind!==`development`||process.env[DEVELOPMENT_WORKFLOW_SECRET_ENV]!==void 0)return validateDeploymentId(await(await getWorld()).getDeploymentId())}function validateDeploymentId(e){if(e.length===0||e===`latest`)throw Error(`Channel ingress could not resolve an exact deployment id.`);return e}function readVercelRequestId(e){let t=e.get(`x-vercel-id`)?.trim();return t===``?void 0:t}function rejectWebSocketUpgrade(e,t){return{upgrade(){throw Response.json(e,{status:t})}}}function flushBackgroundTasks(e,t,n,r){t.length!==0&&e.waitUntil(Promise.allSettled(t).then(e=>{for(let t of e)t.status===`rejected`&&logError(log,`channel background task failed`,t.reason,{routeKey:n,channel:r})}))}function extractRequestIp(e,t){if(t.kind===`development`){let t=readTrustedDevelopmentClientAddress(e.req.headers,process.env[DEVELOPMENT_WORKFLOW_SECRET_ENV]);if(t!==void 0)return t}return extractSocketIp(e)}function extractSocketIp(e){let t=e.req.ip;return typeof t==`string`&&t.length>0?t:null}export{dispatchChannelRequest,dispatchChannelWebSocketRequest};
|
|
1
|
+
import{createAttachSessionFn}from"#channel/session.js";import{createLogger,logError}from"#internal/logging.js";import{readTrustedDevelopmentClientAddress}from"#internal/nitro/dev-client-address.js";import{withVercelOidcProjectResolver}from"#channel/auth/vercel-oidc-project.js";import{createChannelDeliveryMetadata}from"#channel/delivery-metadata.js";import{createChannelOperations}from"#channel/channel-operations.js";import{getChannelInstrumentationKind}from"#channel/compiled-channel.js";import{createCrossChannelToFn,toCrossChannelTargets}from"#channel/cross-channel-receive.js";import{readVercelProjectLink}from"#internal/vercel/project-link.js";import{sessionCommandErrorResponse}from"#eve-channel/support.js";import{getWorld}from"#internal/workflow/runtime.js";import{attachAgentInfoRouteResponse,attachHomeRouteMetadata,attachRemoteAgentStreamHeadersResolver,attachRouteChannelName,attachRouteSessionCreator,attachSkillFileSource}from"#internal/nitro/routes/channel-route-context.js";import{context,trace}from"#compiled/@opentelemetry/api/index.js";import{DEVELOPMENT_WORKFLOW_SECRET_ENV}from"#internal/workflow/development-world-protocol.js";import{resolveNitroCompiledArtifactsSource}from"#internal/nitro/routes/runtime-artifacts.js";import{withLocalDevRequestScope}from"#runtime/local-dev-capability.js";import{createAgentDescriptionRouteArgs}from"#channel/agent-description.js";import{traceChannelRequest}from"#internal/nitro/routes/channel-request-instrumentation.js";import{resolveNitroChannelRuntimeBundle}from"#internal/nitro/routes/runtime-stack.js";import{createRouteInvokeTool}from"#internal/nitro/routes/route-invoke-tool.js";const log=createLogger(`channel.dispatch`);async function dispatchChannelRequest(e,t,n){return await traceChannelRequest({request:e.req,routeKey:t},async r=>{let i=r?.reference??trace.getSpan(context.active())?.spanContext(),a=await resolveNitroChannelRuntimeBundle(n),o=a.channels.find(e=>`${e.method.toUpperCase()} ${e.urlPath}`===t);if(o===void 0)return Response.json({error:`No matching channel for this request.`,ok:!1},{status:404});r?.channel({channelName:o.name});let s=getChannelInstrumentationKind(o.definition)??o.adapter?.kind;s!==void 0&&r?.channel({channelKind:s});let c=await buildRouteArgs(e,a,o.name,s??`channel`,n,i),l;try{l=await withDevelopmentVercelOidcContext(n,e.req,async()=>{if(o.handler)return await o.handler(e.req,c.args);let t={waitUntil:c.args.waitUntil,params:c.args.params,requestIp:c.args.requestIp};return await o.fetch(e.req,t)})}catch(n){let r=sessionCommandErrorResponse(n);if(r!==void 0)return flushBackgroundTasks(e,c.backgroundTasks,t,o.name),r;let i=logError(log,`channel handler threw`,n,{routeKey:t,channel:o.name});return flushBackgroundTasks(e,c.backgroundTasks,t,o.name),Response.json({error:`Channel handler failed.`,errorId:i,ok:!1},{status:500})}return flushBackgroundTasks(e,c.backgroundTasks,t,o.name),l})}async function dispatchChannelWebSocketRequest(e,t,n){let r=await resolveNitroChannelRuntimeBundle(n),i=r.channels.find(e=>`${e.method.toUpperCase()} ${e.urlPath}`===t);if(i===void 0||i.websocket===void 0)return rejectWebSocketUpgrade({error:`No matching websocket channel for this request.`,ok:!1},404);let a=i.websocket,o=getChannelInstrumentationKind(i.definition)??i.adapter?.kind??`channel`,s=await buildRouteArgs(e,r,i.name,o,n,void 0);try{let r=await withDevelopmentVercelOidcContext(n,e.req,async()=>await a(e.req,s.args));return flushBackgroundTasks(e,s.backgroundTasks,t,i.name),r}catch(n){let r=logError(log,`channel websocket handler threw`,n,{routeKey:t,channel:i.name});return flushBackgroundTasks(e,s.backgroundTasks,t,i.name),rejectWebSocketUpgrade({error:`Channel websocket handler failed.`,errorId:r,ok:!1},500)}}async function withDevelopmentVercelOidcContext(e,t,n){return e.kind===`development`?await withLocalDevRequestScope(t,async()=>await withVercelOidcProjectResolver({request:t,resolveCurrentProject:async()=>{let t=await readVercelProjectLink(e.appRoot);return t===void 0?void 0:{environment:`development`,projectId:t.projectId}}},n)):await n()}async function buildRouteArgs(e,t,n,r,i,a){let o=readVercelRequestId(e.req.headers),s=extractRequestIp(e,i),c=[],l=e.context.params??{},u={};for(let[e,t]of Object.entries(l))u[e]=decodeURIComponent(t);let waitUntil=e=>{c.push(e)},d=t.channels.find(e=>e.name===n),f=d?.adapter??{kind:`channel`},p={acceptedDeploymentId:await resolveAcceptedDeploymentId(i),channelKind:r,channelName:n,requestId:o,requestTraceContext:a===void 0?void 0:{spanId:a.spanId,traceFlags:a.traceFlags,traceId:a.traceId}},m=createChannelOperations({adapter:f,channelName:n,metadata:p,runtime:t.runtime,turnPolicy:d?.turnPolicy}),h=createAttachSessionFn(t.runtime,{...p,turnPolicy:d?.turnPolicy}),g=createCrossChannelToFn(t.runtime,toCrossChannelTargets(t.channels),p),_=createAgentDescriptionRouteArgs(()=>resolveNitroCompiledArtifactsSource(i)),v=attachRouteSessionCreator(attachHomeRouteMetadata(attachRouteChannelName(attachAgentInfoRouteResponse({attachSession:h,...m,..._.args,invokeTool:createRouteInvokeTool({agentName:t.agentName,config:i,origin:{adapter:f,agentName:t.agentName,channelName:n},requestUrl:e.req.url}),params:u,requestIp:s,to:g,waitUntil},async()=>{let{handleAgentInfoRequest:e}=await import(`#internal/nitro/routes/info.js`);return await e(i)}),n),{agentName:t.agentName}),async e=>{let{deliveryId:r,...i}=e,a=createChannelDeliveryMetadata(p),s=r===void 0?a:{...a,deliveryId:r};return{...await t.runtime.createSession({...i,adapter:f,channelName:n,continuationToken:e.continuationToken===void 0?void 0:`${n}:${e.continuationToken}`,delivery:s,requestId:o}),deliveryId:s.deliveryId}});return attachSkillFileSource(v,_.skillFiles),t.resolveRemoteAgentStreamHeaders!==void 0&&attachRemoteAgentStreamHeadersResolver(v,t.resolveRemoteAgentStreamHeaders),{args:v,backgroundTasks:c}}async function resolveAcceptedDeploymentId(e){let t=process.env.VERCEL_DEPLOYMENT_ID?.trim();if(t!==void 0&&t.length>0)return validateDeploymentId(t);if(e.kind!==`development`||process.env[DEVELOPMENT_WORKFLOW_SECRET_ENV]!==void 0)return validateDeploymentId(await(await getWorld()).getDeploymentId())}function validateDeploymentId(e){if(e.length===0||e===`latest`)throw Error(`Channel ingress could not resolve an exact deployment id.`);return e}function readVercelRequestId(e){let t=e.get(`x-vercel-id`)?.trim();return t===``?void 0:t}function rejectWebSocketUpgrade(e,t){return{upgrade(){throw Response.json(e,{status:t})}}}function flushBackgroundTasks(e,t,n,r){t.length!==0&&e.waitUntil(Promise.allSettled(t).then(e=>{for(let t of e)t.status===`rejected`&&logError(log,`channel background task failed`,t.reason,{routeKey:n,channel:r})}))}function extractRequestIp(e,t){if(t.kind===`development`){let t=readTrustedDevelopmentClientAddress(e.req.headers,process.env[DEVELOPMENT_WORKFLOW_SECRET_ENV]);if(t!==void 0)return t}return extractSocketIp(e)}function extractSocketIp(e){let t=e.req.ip;return typeof t==`string`&&t.length>0?t:null}export{dispatchChannelRequest,dispatchChannelWebSocketRequest};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{contentPhase}from"#protocol/session-events/catalog.js";import{callLabel}from"#protocol/session-projection/selectors.js";import{createLogger}from"#internal/logging.js";import{parseAnswers}from"#shared/input.js";import{settleSignIn,showSignIn}from"#channel/sign-in.js";import{requestSettlementOf}from"#channel/interaction-prompts.js";import{sessionTables}from"#protocol/session-projection/session-view.js";import{ContextContainer,contextStorage}from"#context/container.js";import{promptQueue}from"#channel/prompt-queue.js";import{failureNotice}from"#public/channels/reply.js";import{GET,POST,defineChannel}from"#public/definitions/channel.js";import{ContextKey}from"#context/key.js";import{DEFAULT_UPLOAD_POLICY}from"#public/channels/upload-policy.js";import{EveAttachmentError}from"#internal/attachments/errors.js";import{assertWithinLimit}from"#internal/attachments/limited-read.js";import{isNotImplemented}from"#public/channels/chat-sdk/notImplemented.js";import{INACTIVE_REQUEST_NOTICE,respondOrRetireInactivePrompt}from"#channel/inactive-prompt.js";import{Chat,Message,ThreadImpl}from"#compiled/chat/index.js";import{chatSdkSignIns}from"#public/channels/chat-sdk/authorization.js";import{parseChatSdkFileRef}from"#public/channels/chat-sdk/attachment-refs.js";import{decodeInputAction,renderInteractions,renderRetiredInputRequest}from"#public/channels/chat-sdk/input-actions.js";import{reportUndeliveredReply,withUndeliveredReplyNote}from"#public/channels/undelivered-reply.js";import{chatSdkInstrumentation}from"#public/channels/chat-sdk/audience.js";const log=createLogger(`chat-sdk.channel`),SURFACE_NAME=`this chat`,ActiveWebhookKey=new ContextKey(`chat-sdk.active-webhook`);function chatSdkChannel(e){let t=new Chat(e),n=e.inputActionPrefix??`eve_input:`,r=e.streaming??!0,i=e.streamingEditIntervalMs??1e3,a={...defaultEvents(n),...e.events};return t.onAction(async r=>{let i=decodeInputAction(r.actionId,n,r.value);if(!i)return;let{thread:a}=r;if(!a)throw Error(`chatSdkChannel input actions require a thread on the Chat SDK action event.`);let o=e.resolveInputAuth?await e.resolveInputAuth(r):null,s=await respondOrRetireInactivePrompt(()=>bridgeRespond(t,[i],{auth:o,thread:a.toJSON()}),{retirePrompt:e=>retireInputCard(a,r.messageId,e)},log);if(s.outcome===`failed`)throw s.error;s.outcome!==`delivered`&&await Promise.all([s.retirement,a.postEphemeral(r.user,{markdown:INACTIVE_REQUEST_NOTICE},{fallbackToDM:!0}).catch(e=>log.error(`inactive prompt notice failed`,{error:e}))])}),{bot:t,channel:defineChannel({turnPolicy:e.turnPolicy,fetchFile:e=>fetchAttachment(t,e),state:initialState(),...chatSdkInstrumentation,context(e){return{bot:t,state:e,streaming:r,streamingEditIntervalMs:i,thread:threadFromState(t,e)}},routes:adapterNames(e.adapters).flatMap(n=>{let r=routeForAdapter(n,e),handler=async(r,i)=>{let a=t.webhooks[n],o=new ContextContainer;return o.setVirtualContext(ActiveWebhookKey,{from:i.from}),contextStorage.run(o,()=>a(r,{...e.webhook,waitUntil(e){i.waitUntil(e)}}))};return[GET(r,handler),POST(r,handler)]}),async receive(e,{from:n}){let r=serializeReceiveTarget(t,e.target);return n(r.id).send(e.message,{auth:e.auth,state:{thread:r}})},deliver(e,t){recordPrincipalUser(t.state,t.session.auth.current,e),recordLatestMessage(t.state,e);let n=resolveInputAnswer(t.state,e);return n===void 0?withUndeliveredReplyNote(t.state,e,SURFACE_NAME):{answers:parseAnswers([n]),context:e.context}},events:a}),send(e,n){return bridgeSend(t,e,n)},respond(e,n){return bridgeRespond(t,e,n)}}}function defaultEvents(e){async function showPrompt(t,n){if(!t.thread)return!1;let r=await t.thread.post(renderInteractions([n],e));r.id&&(t.state.pendingInputCards={...t.state.pendingInputCards,[r.id]:{requests:[n],resolved:{}}})}async function clearAnsweredCards(t,n){let r=n.thread;if(r)for(let[i,a]of Object.entries(n.state.pendingInputCards??{})){let o=t.resolutions.filter(e=>a.requests.some(t=>t.interactionId===e.interactionId));if(o.length===0)continue;let s={...a.resolved,...Object.fromEntries(o.map(e=>[e.interactionId,e]))},{[i]:c,...l}=n.state.pendingInputCards??{};if(n.state.pendingInputCards=a.requests.every(e=>s[e.interactionId]!==void 0)?l:{...l,[i]:{requests:a.requests,resolved:s}},n.state.editSupported!==!1)try{await r.adapter.editMessage(r.id,i,renderInteractions(a.requests,e,s))}catch(e){if(!isNotImplemented(e)){log.warn(`answered input card edit failed`,{error:e,messageId:i});continue}n.state.editSupported=!1}}}return{async"turn.started"(e,{channel:t}){t.state.pendingToolCallMessage=null,clearStream(t.state),await safeStartTyping(t.thread,`Working...`)},async"call.requested"({data:e},{channel:t}){let n=t.state.pendingToolCallMessage;if(t.state.pendingToolCallMessage=null,n){await safeStartTyping(t.thread,truncate(n));return}if(e.capability.kind===`control`)return;let r=callLabel(e);await safeStartTyping(t.thread,truncate(`${r}...`))},async"content.delta"({data:e},{channel:t}){if(!t.thread||!canStream(t))return;let n=t.state.streamPartId===e.partId;if(e.kind===void 0?!n:e.kind!==`text`)return;let r=((n?t.state.streamText:null)??``)+e.delta;if(!r)return;t.state.streamText=r;let i=t.state.anchorMessageId;if(!i||!n){let n=await t.thread.post({markdown:r});t.state.shownText=r,t.state.anchorMessageId=n.id,t.state.streamPartId=e.partId,t.state.lastEditAtMs=Date.now();return}let a=t.state.lastEditAtMs??0;if(!(Date.now()-a<t.streamingEditIntervalMs))try{await editMessage(t.thread,i,r),t.state.shownText=r,t.state.lastEditAtMs=Date.now()}catch(e){if(!isNotImplemented(e))throw e;t.state.editSupported=!1}},async"interaction.opened"(e,t){let{data:n}=e,{channel:r}=t,i=chatSdkSignIns(r);i!==void 0&&await showSignIn(sessionTables(t.view),n.interactionId,i)},async"interaction.settled"(e,t){let{data:n}=e,{channel:r}=t,i=chatSdkSignIns(r);i!==void 0&&await settleSignIn(sessionTables(t.view),n,i);let a=requestSettlementOf(sessionTables(t.view),n);a!==void 0&&await clearAnsweredCards({resolutions:[a]},r)},committed:promptQueue(showPrompt),async"content.completed"({data:e},{channel:t,session:n}){if(e.kind!==`text`)return;let r=typeof e.value==`string`?e.value:``;if(contentPhase(e.phase)===`narration`){if(t.state.pendingToolCallMessage=r?firstNonEmptyLine(r)??null:null,r)try{await finalizeStreamedMessage(t,r,!1)}catch(e){if(!isNotImplemented(e))throw e}else clearStream(t.state);return}if(t.state.pendingToolCallMessage=null,!r){clearStream(t.state);return}try{await finalizeStreamedMessage(t,r,!0)}catch(e){await reportUndeliveredReply({log,name:SURFACE_NAME,post:async e=>t.thread?.post(e),state:t.state},n.turn.id,e)}},async"turn.settled"({data:e},{channel:t}){let n=failureNotice(e,`turn`);n!==void 0&&await t.thread?.post(n)},async"session.ended"({data:e},{channel:t}){let n=failureNotice(e,{restart:`message`});n!==void 0&&await t.thread?.post(n)}}}async function safeStartTyping(e,t){if(e)try{await e.startTyping(t)}catch(e){if(!isNotImplemented(e))throw e}}async function editMessage(e,t,n){await e.adapter.editMessage(e.id,t,{markdown:n})}async function retireInputCard(e,t,n){try{await e.adapter.editMessage(e.id,t,renderRetiredInputRequest(n))}catch(e){if(!isNotImplemented(e))throw e}}async function finalizeStreamedMessage(e,t,n){let{anchorMessageId:r,shownText:i}=e.state;clearStream(e.state);let a=e.thread;if(a){if(!r){n&&await a.post({markdown:t});return}if(i!==t)try{await editMessage(a,r,t)}catch(t){throw isNotImplemented(t)&&(e.state.editSupported=!1),t}}}function canStream(e){return e.streaming&&e.state.editSupported!==!1}function clearStream(e){e.anchorMessageId=null,e.lastEditAtMs=null,e.streamPartId=null,e.streamText=null,e.shownText=null}function firstNonEmptyLine(e){for(let t of e.split(/\r?\n/u)){let e=t.trim();if(e.length>0)return e}}function truncate(e,t=80){return e.length<=t?e:`${e.slice(0,t-1)}\u2026`}async function bridgeSend(e,t,n){let r=activeFrom(`send`),{adapterName:i,auth:a,inputAnswer:o,thread:s,...c}=n,l=serializeThread(e,s,i),u={thread:l};return o!==void 0&&(u.inputAnswer=o),r(l.id).send(t,{...c,auth:a??null,state:u})}async function bridgeRespond(e,t,n){let r=activeFrom(`respond`),{adapterName:i,auth:a,thread:o,...s}=n;return r(serializeThread(e,o,i).id).respond(parseAnswers(t),{...s,auth:a??null})}function recordPrincipalUser(e,t,n){if(t===null)return;let r=(n.state?.thread)?.currentMessage?.author;if(r===void 0||r.isBot===!0||r.isMe)return;let i=e.usersByPrincipal?.[t.principalId];i!==r.userId&&i!==null&&(e.usersByPrincipal={...e.usersByPrincipal,[t.principalId]:i===void 0?r.userId:null})}function recordLatestMessage(e,t){let n=t.state?.thread?.currentMessage;n===void 0||n.author.isMe||(e.latestMessageId=n.id)}function resolveInputAnswer(e,t){let n=t.state?.inputAnswer;if(n===void 0)return;let r=e.pendingInputCards?.[n.messageId],i=r?.requests.find(e=>r.resolved[e.interactionId]===void 0&&e.request.kind!==`sign-in`&&e.request.options?.some(e=>e.id===n.optionId)===!0);return i===void 0?void 0:{optionId:n.optionId,interactionId:i.interactionId}}function activeFrom(e){let t=contextStorage.getStore()?.get(ActiveWebhookKey);if(!t)throw Error(`chatSdkChannel().${e} can only run during a Chat SDK webhook handler for this bridge.`);return t.from}function initialState(){return{pendingAuthMessageIds:{},thread:null}}async function fetchAttachment(e,t){let n=parseChatSdkFileRef(new URL(t));if(n===null)return null;await e.initialize();let r=e.getAdapter(n.adapter).rehydrateAttachment?.(n.attachment);if(r?.fetchData===void 0)throw new EveAttachmentError({adapterKind:`chat-sdk`,kind:`resolver-threw`,message:`the ${n.adapter} adapter can't download it after the webhook returns.`});let i;try{i=await r.fetchData()}catch(e){throw e instanceof EveAttachmentError?e:new EveAttachmentError({adapterKind:`chat-sdk`,cause:e,kind:`resolver-threw`,message:`the ${n.adapter} download failed.`})}let a=Buffer.isBuffer(i)?i:Buffer.from(i);return assertWithinLimit(a,DEFAULT_UPLOAD_POLICY.maxBytes,`chat-sdk`),{bytes:a,mediaType:n.attachment.mimeType}}function threadFromState(e,t){if(!t.thread)return null;try{let n=t.thread;return new ThreadImpl({adapter:e.getAdapter(n.adapterName),channelId:n.channelId,channelVisibility:n.channelVisibility,currentMessage:n.currentMessage?Message.fromJSON(n.currentMessage):void 0,id:n.id,isDM:n.isDM,stateAdapter:e.getState()})}catch(e){return log.warn(`failed to rebuild Chat SDK thread from channel state`,{error:e}),null}}function serializeReceiveTarget(e,t){if(t.thread)return t.thread;if(!t.threadId)throw Error(`chatSdkChannel().receive requires target.thread or target.threadId.`);return serializeThread(e,t.threadId,t.adapterName)}function serializeThread(e,t,n){if(typeof t==`string`){let r=n??inferAdapterName(t),i=e.getAdapter(r);return{_type:`chat:Thread`,adapterName:r,channelId:i.channelIdFromThreadId(t),channelVisibility:i.getChannelVisibility?.(t),id:t,isDM:!1}}return isSerializedThread(t)?t:t.toJSON()}function isSerializedThread(e){return`_type`in e&&e._type===`chat:Thread`}function inferAdapterName(e){let t=e.indexOf(`:`);if(t<=0)throw Error(`chatSdkChannel string thread references require options.adapterName.`);return e.slice(0,t)}function adapterNames(e){return Object.keys(e)}function routeForAdapter(e,t){return t.routes?.[e]||`${(t.route??`/eve/v1`).replace(/\/$/u,``)}/${e}`}export{chatSdkChannel};
|
|
1
|
+
import{contentPhase}from"#protocol/session-events/catalog.js";import{callLabel}from"#protocol/session-projection/selectors.js";import{createLogger}from"#internal/logging.js";import{parseAnswers}from"#shared/input.js";import{settleSignIn,showSignIn}from"#channel/sign-in.js";import{requestSettlementOf}from"#channel/interaction-prompts.js";import{sessionTables}from"#protocol/session-projection/session-view.js";import{ContextContainer,contextStorage}from"#context/container.js";import{promptQueue}from"#channel/prompt-queue.js";import{failureNotice}from"#public/channels/reply.js";import{GET,POST,defineChannel}from"#public/definitions/channel.js";import{ContextKey}from"#context/key.js";import{DEFAULT_UPLOAD_POLICY}from"#public/channels/upload-policy.js";import{EveAttachmentError}from"#internal/attachments/errors.js";import{assertWithinLimit}from"#internal/attachments/limited-read.js";import{parseChatSdkFileRef}from"#public/channels/chat-sdk/attachment-refs.js";import{isNotImplemented}from"#public/channels/chat-sdk/notImplemented.js";import{INACTIVE_REQUEST_NOTICE,respondOrRetireInactivePrompt}from"#channel/inactive-prompt.js";import{Chat,Message,ThreadImpl}from"#compiled/chat/index.js";import{chatSdkSignIns}from"#public/channels/chat-sdk/authorization.js";import{decodeInputAction,renderInteractions,renderRetiredInputRequest}from"#public/channels/chat-sdk/input-actions.js";import{reportUndeliveredReply,withUndeliveredReplyNote}from"#public/channels/undelivered-reply.js";import{chatSdkInstrumentation}from"#public/channels/chat-sdk/audience.js";const log=createLogger(`chat-sdk.channel`),SURFACE_NAME=`this chat`,ActiveWebhookKey=new ContextKey(`chat-sdk.active-webhook`);function chatSdkChannel(e){let t=new Chat(e),n=e.inputActionPrefix??`eve_input:`,r=e.streaming??!0,i=e.streamingEditIntervalMs??1e3,a={...defaultEvents(n),...e.events};return t.onAction(async r=>{let i=decodeInputAction(r.actionId,n,r.value);if(!i)return;let{thread:a}=r;if(!a)throw Error(`chatSdkChannel input actions require a thread on the Chat SDK action event.`);let o=e.resolveInputAuth?await e.resolveInputAuth(r):null,s=await respondOrRetireInactivePrompt(()=>bridgeRespond(t,[i],{auth:o,thread:a.toJSON()}),{retirePrompt:e=>retireInputCard(a,r.messageId,e)},log);if(s.outcome===`failed`)throw s.error;s.outcome!==`delivered`&&await Promise.all([s.retirement,a.postEphemeral(r.user,{markdown:INACTIVE_REQUEST_NOTICE},{fallbackToDM:!0}).catch(e=>log.error(`inactive prompt notice failed`,{error:e}))])}),{bot:t,channel:defineChannel({turnPolicy:e.turnPolicy,fetchFile:e=>fetchAttachment(t,e),state:initialState(),...chatSdkInstrumentation,context(e){return{bot:t,state:e,streaming:r,streamingEditIntervalMs:i,thread:threadFromState(t,e)}},routes:adapterNames(e.adapters).flatMap(n=>{let r=routeForAdapter(n,e),handler=async(r,i)=>{let a=t.webhooks[n],o=new ContextContainer;return o.setVirtualContext(ActiveWebhookKey,{from:i.from}),contextStorage.run(o,()=>a(r,{...e.webhook,waitUntil(e){i.waitUntil(e)}}))};return[GET(r,handler),POST(r,handler)]}),async receive({message:e,target:n,...r},{from:i}){let a=serializeReceiveTarget(t,n);return i(a.id).send(e,{...r,state:{thread:a}})},deliver(e,t){recordPrincipalUser(t.state,t.session.auth.current,e),recordLatestMessage(t.state,e);let n=resolveInputAnswer(t.state,e);return n===void 0?withUndeliveredReplyNote(t.state,e,SURFACE_NAME):{answers:parseAnswers([n]),context:e.context}},events:a}),send(e,n){return bridgeSend(t,e,n)},respond(e,n){return bridgeRespond(t,e,n)}}}function defaultEvents(e){async function showPrompt(t,n){if(!t.thread)return!1;let r=await t.thread.post(renderInteractions([n],e));r.id&&(t.state.pendingInputCards={...t.state.pendingInputCards,[r.id]:{requests:[n],resolved:{}}})}async function clearAnsweredCards(t,n){let r=n.thread;if(r)for(let[i,a]of Object.entries(n.state.pendingInputCards??{})){let o=t.resolutions.filter(e=>a.requests.some(t=>t.interactionId===e.interactionId));if(o.length===0)continue;let s={...a.resolved,...Object.fromEntries(o.map(e=>[e.interactionId,e]))},{[i]:c,...l}=n.state.pendingInputCards??{};if(n.state.pendingInputCards=a.requests.every(e=>s[e.interactionId]!==void 0)?l:{...l,[i]:{requests:a.requests,resolved:s}},n.state.editSupported!==!1)try{await r.adapter.editMessage(r.id,i,renderInteractions(a.requests,e,s))}catch(e){if(!isNotImplemented(e)){log.warn(`answered input card edit failed`,{error:e,messageId:i});continue}n.state.editSupported=!1}}}return{async"turn.started"(e,{channel:t}){t.state.pendingToolCallMessage=null,clearStream(t.state),await safeStartTyping(t.thread,`Working...`)},async"call.requested"({data:e},{channel:t}){let n=t.state.pendingToolCallMessage;if(t.state.pendingToolCallMessage=null,n){await safeStartTyping(t.thread,truncate(n));return}if(e.capability.kind===`control`)return;let r=callLabel(e);await safeStartTyping(t.thread,truncate(`${r}...`))},async"content.delta"({data:e},{channel:t}){if(!t.thread||!canStream(t))return;let n=t.state.streamPartId===e.partId;if(e.kind===void 0?!n:e.kind!==`text`)return;let r=((n?t.state.streamText:null)??``)+e.delta;if(!r)return;t.state.streamText=r;let i=t.state.anchorMessageId;if(!i||!n){let n=await t.thread.post({markdown:r});t.state.shownText=r,t.state.anchorMessageId=n.id,t.state.streamPartId=e.partId,t.state.lastEditAtMs=Date.now();return}let a=t.state.lastEditAtMs??0;if(!(Date.now()-a<t.streamingEditIntervalMs))try{await editMessage(t.thread,i,r),t.state.shownText=r,t.state.lastEditAtMs=Date.now()}catch(e){if(!isNotImplemented(e))throw e;t.state.editSupported=!1}},async"interaction.opened"(e,t){let{data:n}=e,{channel:r}=t,i=chatSdkSignIns(r);i!==void 0&&await showSignIn(sessionTables(t.view),n.interactionId,i)},async"interaction.settled"(e,t){let{data:n}=e,{channel:r}=t,i=chatSdkSignIns(r);i!==void 0&&await settleSignIn(sessionTables(t.view),n,i);let a=requestSettlementOf(sessionTables(t.view),n);a!==void 0&&await clearAnsweredCards({resolutions:[a]},r)},committed:promptQueue(showPrompt),async"content.completed"({data:e},{channel:t,session:n}){if(e.kind!==`text`)return;let r=typeof e.value==`string`?e.value:``;if(contentPhase(e.phase)===`narration`){if(t.state.pendingToolCallMessage=r?firstNonEmptyLine(r)??null:null,r)try{await finalizeStreamedMessage(t,r,!1)}catch(e){if(!isNotImplemented(e))throw e}else clearStream(t.state);return}if(t.state.pendingToolCallMessage=null,!r){clearStream(t.state);return}try{await finalizeStreamedMessage(t,r,!0)}catch(e){await reportUndeliveredReply({log,name:SURFACE_NAME,post:async e=>t.thread?.post(e),state:t.state},n.turn.id,e)}},async"turn.settled"({data:e},{channel:t}){let n=failureNotice(e,`turn`);n!==void 0&&await t.thread?.post(n)},async"session.ended"({data:e},{channel:t}){let n=failureNotice(e,{restart:`message`});n!==void 0&&await t.thread?.post(n)}}}async function safeStartTyping(e,t){if(e)try{await e.startTyping(t)}catch(e){if(!isNotImplemented(e))throw e}}async function editMessage(e,t,n){await e.adapter.editMessage(e.id,t,{markdown:n})}async function retireInputCard(e,t,n){try{await e.adapter.editMessage(e.id,t,renderRetiredInputRequest(n))}catch(e){if(!isNotImplemented(e))throw e}}async function finalizeStreamedMessage(e,t,n){let{anchorMessageId:r,shownText:i}=e.state;clearStream(e.state);let a=e.thread;if(a){if(!r){n&&await a.post({markdown:t});return}if(i!==t)try{await editMessage(a,r,t)}catch(t){throw isNotImplemented(t)&&(e.state.editSupported=!1),t}}}function canStream(e){return e.streaming&&e.state.editSupported!==!1}function clearStream(e){e.anchorMessageId=null,e.lastEditAtMs=null,e.streamPartId=null,e.streamText=null,e.shownText=null}function firstNonEmptyLine(e){for(let t of e.split(/\r?\n/u)){let e=t.trim();if(e.length>0)return e}}function truncate(e,t=80){return e.length<=t?e:`${e.slice(0,t-1)}\u2026`}async function bridgeSend(e,t,n){let r=activeFrom(`send`),{adapterName:i,auth:a,inputAnswer:o,thread:s,...c}=n,l=serializeThread(e,s,i),u={thread:l};return o!==void 0&&(u.inputAnswer=o),r(l.id).send(t,{...c,auth:a??null,state:u})}async function bridgeRespond(e,t,n){let r=activeFrom(`respond`),{adapterName:i,auth:a,thread:o,...s}=n;return r(serializeThread(e,o,i).id).respond(parseAnswers(t),{...s,auth:a??null})}function recordPrincipalUser(e,t,n){if(t===null)return;let r=(n.state?.thread)?.currentMessage?.author;if(r===void 0||r.isBot===!0||r.isMe)return;let i=e.usersByPrincipal?.[t.principalId];i!==r.userId&&i!==null&&(e.usersByPrincipal={...e.usersByPrincipal,[t.principalId]:i===void 0?r.userId:null})}function recordLatestMessage(e,t){let n=t.state?.thread?.currentMessage;n===void 0||n.author.isMe||(e.latestMessageId=n.id)}function resolveInputAnswer(e,t){let n=t.state?.inputAnswer;if(n===void 0)return;let r=e.pendingInputCards?.[n.messageId],i=r?.requests.find(e=>r.resolved[e.interactionId]===void 0&&e.request.kind!==`sign-in`&&e.request.options?.some(e=>e.id===n.optionId)===!0);return i===void 0?void 0:{optionId:n.optionId,interactionId:i.interactionId}}function activeFrom(e){let t=contextStorage.getStore()?.get(ActiveWebhookKey);if(!t)throw Error(`chatSdkChannel().${e} can only run during a Chat SDK webhook handler for this bridge.`);return t.from}function initialState(){return{pendingAuthMessageIds:{},thread:null}}async function fetchAttachment(e,t){let n=parseChatSdkFileRef(new URL(t));if(n===null)return null;await e.initialize();let r=e.getAdapter(n.adapter).rehydrateAttachment?.(n.attachment);if(r?.fetchData===void 0)throw new EveAttachmentError({adapterKind:`chat-sdk`,kind:`resolver-threw`,message:`the ${n.adapter} adapter can't download it after the webhook returns.`});let i;try{i=await r.fetchData()}catch(e){throw e instanceof EveAttachmentError?e:new EveAttachmentError({adapterKind:`chat-sdk`,cause:e,kind:`resolver-threw`,message:`the ${n.adapter} download failed.`})}let a=Buffer.isBuffer(i)?i:Buffer.from(i);return assertWithinLimit(a,DEFAULT_UPLOAD_POLICY.maxBytes,`chat-sdk`),{bytes:a,mediaType:n.attachment.mimeType}}function threadFromState(e,t){if(!t.thread)return null;try{let n=t.thread;return new ThreadImpl({adapter:e.getAdapter(n.adapterName),channelId:n.channelId,channelVisibility:n.channelVisibility,currentMessage:n.currentMessage?Message.fromJSON(n.currentMessage):void 0,id:n.id,isDM:n.isDM,stateAdapter:e.getState()})}catch(e){return log.warn(`failed to rebuild Chat SDK thread from channel state`,{error:e}),null}}function serializeReceiveTarget(e,t){if(t.thread)return t.thread;if(!t.threadId)throw Error(`chatSdkChannel().receive requires target.thread or target.threadId.`);return serializeThread(e,t.threadId,t.adapterName)}function serializeThread(e,t,n){if(typeof t==`string`){let r=n??inferAdapterName(t),i=e.getAdapter(r);return{_type:`chat:Thread`,adapterName:r,channelId:i.channelIdFromThreadId(t),channelVisibility:i.getChannelVisibility?.(t),id:t,isDM:!1}}return isSerializedThread(t)?t:t.toJSON()}function isSerializedThread(e){return`_type`in e&&e._type===`chat:Thread`}function inferAdapterName(e){let t=e.indexOf(`:`);if(t<=0)throw Error(`chatSdkChannel string thread references require options.adapterName.`);return e.slice(0,t)}function adapterNames(e){return Object.keys(e)}function routeForAdapter(e,t){return t.routes?.[e]||`${(t.route??`/eve/v1`).replace(/\/$/u,``)}/${e}`}export{chatSdkChannel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{discordAudience,discordInstrumentationMetadata}from"./audience.js";import{createLogger,logError}from"#internal/logging.js";import{readNonEmptyString}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{INACTIVE_REQUEST_NOTICE,respondOrRetireInactivePrompt}from"#channel/inactive-prompt.js";import{buildFreeformModalResponse,deriveComponentAnswers,deriveModalAnswers,isDiscordFreeformComponent}from"#public/channels/discord/hitl.js";import{callDiscordApi,createDiscordFollowupMessage,discordContinuationToken,editDiscordOriginalResponse,resolveDiscordBotToken,sendDiscordChannelMessage,splitDiscordMessageContent,triggerDiscordTypingIndicator}from"#public/channels/discord/api.js";import{answerDiscordAuthorizationReveal,readDiscordAuthorizationReveal}from"#public/channels/discord/authorization.js";import{defaultDiscordAuth,defaultEvents,defaultOnCommand,editDiscordResolvedPrompt}from"#public/channels/discord/defaults.js";import{DISCORD_EPHEMERAL_MESSAGE_FLAG,DISCORD_INTERACTION_RESPONSE_TYPE,DISCORD_INTERACTION_TYPE,commandInteractionMessage,formatDiscordContextBlock,parseDiscordInteraction}from"#public/channels/discord/inbound.js";import{discordDeferredJson,discordJson,discordJsonBody,readMessageContent}from"#public/channels/discord/responses.js";import{createDiscordGatewayRoute}from"#public/channels/discord/messages.js";import{verifyDiscordInbound}from"#public/channels/discord/verifyInbound.js";import{createDiscordFetchFile,discordCommandContent}from"#public/channels/discord/attachments.js";const log=createLogger(`discord.channel`);function discordChannel(e={}){let t=e.onCommand??defaultOnCommand,n=e.onAnswers??(e.onCommand===void 0?acceptAsPresser:dropUnmappedAnswer),r={...defaultEvents,...e.events};return defineChannel({turnPolicy:e.turnPolicy,state:initialDiscordState(),metadata:discordInstrumentationMetadata,audience:({state:e})=>e.audience??`unknown`,fetchFile:createDiscordFetchFile(e.api?.fetch),context(t,n){return rebuildDiscordContext(t,n,e)},routes:[POST(e.route??`/eve/v1/discord`,async(r,{from:i,resolveSession:a,waitUntil:o})=>{let s=await verifyDiscordInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Discord body is not valid JSON`,{error:e}),discordJson({content:`invalid request`,ephemeral:!0})}if(c.type===DISCORD_INTERACTION_TYPE.PING)return discordJson({type:DISCORD_INTERACTION_RESPONSE_TYPE.PONG});let l=parseDiscordInteraction(c);return l===null?discordJson({content:`Unsupported Discord interaction.`,ephemeral:!0}):handleInteraction({config:e,interaction:l,onCommand:t,onAnswers:n,from:i,resolveSession:a,waitUntil:o})}),...e.gateway===void 0?[]:[createDiscordGatewayRoute({buildContext:t=>({discord:buildDiscordHandle({config:e,state:t})}),config:e,gateway:e.gateway})]],async receive(t,{from:n}){let r=t.target,i=readNonEmptyString(r.channelId);if(!i)throw Error(`discordChannel().receive requires target.channelId.`);let a=readNonEmptyString(r.conversationId),o=r.initialMessage;if(o!==void 0&&a!==void 0)throw Error("discordChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let s=a??``,c=a!==void 0;if(o!==void 0){let t=await buildDiscordHandle({config:e,state:{...initialDiscordState(),channelId:i}}).sendChannelMessage(o);s=t.id,c=t.id.length>0}return n(discordContinuationToken({channelId:i,conversationId:s})).send(t.message,{auth:t.auth,state:{applicationId:null,channelId:i,conversationId:s||null,guildId:null,hasMessageAnchor:c,initialResponseSent:!0,interactionToken:null}})},events:r})}function rebuildDiscordContext(e,t,n){return{discord:buildDiscordHandle({config:n,session:t,state:e}),state:e}}function buildDiscordHandle(e){let t=e.config.api,n=e.state,r=mergeCredentials(e.config.credentials,n);function anchor(t){t.id&&(n.hasMessageAnchor||=(n.conversationId=t.id,!0),n.channelId&&e.session?.continuation?.alias(discordContinuationToken({channelId:n.channelId,conversationId:t.id})))}async function sendViaChannel(e){let i=n.channelId??``;if(!i)throw Error(`discordChannel: missing channel id for outbound message.`);let a=normalizePostInput(e),o=await sendDiscordChannelMessage({apiBaseUrl:t?.apiBaseUrl,body:a,credentials:r,fetch:t?.fetch,channelId:i});return anchor(o),o}async function editOriginal(e){let i=n.interactionToken??``;if(!i)throw Error(`discordChannel: missing interaction token for original response edit.`);let a=normalizePostInput(e),o=await editDiscordOriginalResponse({apiBaseUrl:t?.apiBaseUrl,body:a,credentials:r,fetch:t?.fetch,interactionToken:i});return n.initialResponseSent=!0,anchor(o),o}async function followup(e){let i=n.interactionToken??``;if(!i)throw Error(`discordChannel: missing interaction token for followup message.`);let a=normalizePostInput(e),o=await createDiscordFollowupMessage({apiBaseUrl:t?.apiBaseUrl,body:a,credentials:r,fetch:t?.fetch,interactionToken:i});return anchor(o),o}async function startTyping(){let e=n.channelId??``;if(e)try{await triggerDiscordTypingIndicator({apiBaseUrl:t?.apiBaseUrl,credentials:r,fetch:t?.fetch,channelId:e})}catch(t){logError(log,`Discord typing indicator failed — swallowed`,t,{channelId:e})}}return{applicationId:n.applicationId??void 0,channelId:n.channelId??``,conversationId:n.conversationId??``,guildId:n.guildId??void 0,interactionToken:n.interactionToken??void 0,request(e,n,i){return callDiscordApi({apiBaseUrl:t?.apiBaseUrl,body:n,botToken:i?.botAuth===!0?r.botToken:void 0,fetch:t?.fetch,method:i?.method,path:e})},async post(e){let t=expandPostBodies(normalizePostInput(e)),r;for(let e of t){let t=await postOne({body:e,editOriginal,followup,sendViaChannel,state:n});r===void 0&&(r=t)}return r??{id:``,raw:null}},editOriginalResponse:editOriginal,followup,sendChannelMessage:sendViaChannel,startTyping}}async function postOne(e){if(e.state.interactionToken&&e.state.applicationId)try{return e.state.initialResponseSent?await e.followup(e.body):await e.editOriginal(e.body)}catch(e){log.warn(`Discord interaction-token delivery failed, falling back to channel message`,{error:e})}return e.sendViaChannel(e.body)}async function handleInteraction(e){if(e.interaction.type===DISCORD_INTERACTION_TYPE.MESSAGE_COMPONENT){let t=readDiscordAuthorizationReveal(e.interaction.customId);if(t!==null)return revealAuthorization({...e,interaction:e.interaction,reveal:t})}return e.interaction.type===DISCORD_INTERACTION_TYPE.APPLICATION_COMMAND?handleCommandInteraction({config:e.config,interaction:e.interaction,onCommand:e.onCommand,from:e.from,waitUntil:e.waitUntil}):e.interaction.type===DISCORD_INTERACTION_TYPE.MESSAGE_COMPONENT?handleComponentInteraction({config:e.config,interaction:e.interaction,onAnswers:e.onAnswers,from:e.from,waitUntil:e.waitUntil}):handleModalSubmitInteraction({config:e.config,interaction:e.interaction,onAnswers:e.onAnswers,from:e.from,waitUntil:e.waitUntil})}async function handleCommandInteraction(e){let t=stateFromInteraction(e.interaction,{conversationId:e.interaction.id,hasMessageAnchor:!1,initialResponseSent:!1}),n={discord:buildDiscordHandle({config:e.config,state:t})},r;try{r=await e.onCommand(n,e.interaction)}catch(e){return log.error(`command handler failed`,{error:e}),discordJson({content:`The Discord command handler failed.`,ephemeral:!0})}return r==null?discordJson({content:`Command ignored.`,ephemeral:!0}):(e.waitUntil(dispatchCommand({interaction:e.interaction,result:r,from:e.from,state:t})),discordDeferredJson(r.ephemeral===!0))}function revealAuthorization(e){return e.waitUntil(answerRevealedAuthorization(e)),discordDeferredJson(!0)}async function answerRevealedAuthorization(e){let{interaction:t}=e,n=stateFromInteraction(t,{conversationId:t.messageId,hasMessageAnchor:!0,initialResponseSent:!1}),r=buildDiscordHandle({config:e.config,state:n}),i=null;try{i=(await e.onAnswers({defaultAuth:defaultDiscordAuth(t),discord:r},t))?.auth??null}catch(e){log.error(`Discord sign-in press authorization failed`,{error:e})}let a=await answerDiscordAuthorizationReveal({auth:i,resolveSession:()=>e.resolveSession(discordContinuationToken({channelId:t.channelId,conversationId:t.messageId})),reveal:e.reveal});try{await r.editOriginalResponse(a)}catch(e){log.error(`Discord sign-in reply failed`,{error:e})}}function handleComponentInteraction(e){if(isDiscordFreeformComponent(e.interaction.customId)){let t=readMessageContent(e.interaction.raw);return discordJsonBody(buildFreeformModalResponse({customId:e.interaction.customId,prompt:t}))}let t=deriveComponentAnswers(e.interaction);return t.length>0&&e.waitUntil(dispatchAnswers({config:e.config,onAnswers:e.onAnswers,conversationId:e.interaction.messageId,answers:t,interaction:e.interaction,from:e.from})),discordJsonBody({type:DISCORD_INTERACTION_RESPONSE_TYPE.DEFERRED_UPDATE_MESSAGE})}function handleModalSubmitInteraction(e){let t=deriveModalAnswers(e.interaction);return t.length>0&&e.waitUntil(dispatchAnswers({config:e.config,onAnswers:e.onAnswers,conversationId:e.interaction.messageId??e.interaction.id,answers:t,interaction:e.interaction,from:e.from})),discordJson({content:`Answer received.`,ephemeral:!0})}async function dispatchCommand(e){let t=discordCommandContent(commandInteractionMessage(e.interaction),e.interaction.attachments),n=formatDiscordContextBlock({applicationId:e.interaction.applicationId,channelId:e.interaction.channelId,commandName:e.interaction.commandName,guildId:e.interaction.guildId,interactionId:e.interaction.id,userId:e.interaction.user.id,username:e.interaction.user.username}),r=e.result.context??[];try{await e.from(discordContinuationToken({channelId:e.interaction.channelId,conversationId:e.interaction.id})).send(t,{auth:e.result.auth,context:[n,...r],history:e.result.history,state:e.state,title:e.result.title})}catch(e){log.error(`command delivery failed`,{error:e})}}async function dispatchAnswers(e){let t=stateFromInteraction(e.interaction,{conversationId:e.conversationId,hasMessageAnchor:e.conversationId!==e.interaction.id,initialResponseSent:!0}),n=buildDiscordHandle({config:e.config,state:t}),r;try{r=await e.onAnswers({defaultAuth:defaultDiscordAuth(e.interaction),discord:n},e.interaction)}catch(e){log.error(`input response handler failed`,{error:e});return}if(r===null)return;let{auth:i}=r,a=e.from(discordContinuationToken({channelId:e.interaction.channelId,conversationId:e.conversationId})),o=await respondOrRetireInactivePrompt(()=>a.respond(e.answers,{auth:i}),{retirePrompt:async t=>{let r=e.interaction.messageId,i=readMessageContent(e.interaction.raw);r!==void 0&&i!==void 0&&await editDiscordResolvedPrompt(n,{content:i,messageId:r},t)}},log);o.outcome===`inactive`&&await Promise.all([o.retirement,sendInactiveNotice(n)])}async function sendInactiveNotice(e){try{await e.followup({content:INACTIVE_REQUEST_NOTICE,flags:DISCORD_EPHEMERAL_MESSAGE_FLAG})}catch(e){log.error(`inactive prompt notice failed`,{error:e})}}function acceptAsPresser(e){return{auth:e.defaultAuth}}function dropUnmappedAnswer(){return log.warn(`dropped a Discord input response: onCommand is customized but onAnswers is not; set onAnswers to accept button presses and modal submissions`),null}function stateFromInteraction(e,t){return{audience:discordAudience(e.channelType),applicationId:e.applicationId,...e.channelType!==void 0&&{channelType:e.channelType},channelId:e.channelId,conversationId:t.conversationId,guildId:e.guildId??null,hasMessageAnchor:t.hasMessageAnchor,initialResponseSent:t.initialResponseSent,interactionToken:e.token}}function initialDiscordState(){return{applicationId:null,channelId:null,conversationId:null,guildId:null,hasMessageAnchor:!1,initialResponseSent:!1,interactionToken:null}}function mergeCredentials(e,t){return{applicationId:t.applicationId??e?.applicationId,botToken:e?.botToken??(()=>resolveDiscordBotToken()),publicKey:e?.publicKey,webhookVerifier:e?.webhookVerifier}}function normalizePostInput(e){return typeof e==`string`?{content:e}:e}function expandPostBodies(e){return typeof e.content==`string`?splitDiscordMessageContent(e.content).map((t,n)=>n===0?{...e,content:t}:{allowed_mentions:e.allowed_mentions,content:t}):[e]}export{discordChannel};
|
|
1
|
+
import{discordAudience,discordInstrumentationMetadata}from"./audience.js";import{createLogger,logError}from"#internal/logging.js";import{readNonEmptyString}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{INACTIVE_REQUEST_NOTICE,respondOrRetireInactivePrompt}from"#channel/inactive-prompt.js";import{buildFreeformModalResponse,deriveComponentAnswers,deriveModalAnswers,isDiscordFreeformComponent}from"#public/channels/discord/hitl.js";import{callDiscordApi,createDiscordFollowupMessage,discordContinuationToken,editDiscordOriginalResponse,resolveDiscordBotToken,sendDiscordChannelMessage,splitDiscordMessageContent,triggerDiscordTypingIndicator}from"#public/channels/discord/api.js";import{answerDiscordAuthorizationReveal,readDiscordAuthorizationReveal}from"#public/channels/discord/authorization.js";import{defaultDiscordAuth,defaultEvents,defaultOnCommand,editDiscordResolvedPrompt}from"#public/channels/discord/defaults.js";import{DISCORD_EPHEMERAL_MESSAGE_FLAG,DISCORD_INTERACTION_RESPONSE_TYPE,DISCORD_INTERACTION_TYPE,commandInteractionMessage,formatDiscordContextBlock,parseDiscordInteraction}from"#public/channels/discord/inbound.js";import{discordDeferredJson,discordJson,discordJsonBody,readMessageContent}from"#public/channels/discord/responses.js";import{createDiscordGatewayRoute}from"#public/channels/discord/messages.js";import{verifyDiscordInbound}from"#public/channels/discord/verifyInbound.js";import{createDiscordFetchFile,discordCommandContent}from"#public/channels/discord/attachments.js";const log=createLogger(`discord.channel`);function discordChannel(e={}){let t=e.onCommand??defaultOnCommand,n=e.onAnswers??(e.onCommand===void 0?acceptAsPresser:dropUnmappedAnswer),r={...defaultEvents,...e.events};return defineChannel({turnPolicy:e.turnPolicy,state:initialDiscordState(),metadata:discordInstrumentationMetadata,audience:({state:e})=>e.audience??`unknown`,fetchFile:createDiscordFetchFile(e.api?.fetch),context(t,n){return rebuildDiscordContext(t,n,e)},routes:[POST(e.route??`/eve/v1/discord`,async(r,{from:i,resolveSession:a,waitUntil:o})=>{let s=await verifyDiscordInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Discord body is not valid JSON`,{error:e}),discordJson({content:`invalid request`,ephemeral:!0})}if(c.type===DISCORD_INTERACTION_TYPE.PING)return discordJson({type:DISCORD_INTERACTION_RESPONSE_TYPE.PONG});let l=parseDiscordInteraction(c);return l===null?discordJson({content:`Unsupported Discord interaction.`,ephemeral:!0}):handleInteraction({config:e,interaction:l,onCommand:t,onAnswers:n,from:i,resolveSession:a,waitUntil:o})}),...e.gateway===void 0?[]:[createDiscordGatewayRoute({buildContext:t=>({discord:buildDiscordHandle({config:e,state:t})}),config:e,gateway:e.gateway})]],async receive({message:t,target:n,...r},{from:i}){let a=n,o=readNonEmptyString(a.channelId);if(!o)throw Error(`discordChannel().receive requires target.channelId.`);let s=readNonEmptyString(a.conversationId),c=a.initialMessage;if(c!==void 0&&s!==void 0)throw Error("discordChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let l=s??``,u=s!==void 0;if(c!==void 0){let t=await buildDiscordHandle({config:e,state:{...initialDiscordState(),channelId:o}}).sendChannelMessage(c);l=t.id,u=t.id.length>0}return i(discordContinuationToken({channelId:o,conversationId:l})).send(t,{...r,state:{applicationId:null,channelId:o,conversationId:l||null,guildId:null,hasMessageAnchor:u,initialResponseSent:!0,interactionToken:null}})},events:r})}function rebuildDiscordContext(e,t,n){return{discord:buildDiscordHandle({config:n,session:t,state:e}),state:e}}function buildDiscordHandle(e){let t=e.config.api,n=e.state,r=mergeCredentials(e.config.credentials,n);function anchor(t){t.id&&(n.hasMessageAnchor||=(n.conversationId=t.id,!0),n.channelId&&e.session?.continuation?.alias(discordContinuationToken({channelId:n.channelId,conversationId:t.id})))}async function sendViaChannel(e){let i=n.channelId??``;if(!i)throw Error(`discordChannel: missing channel id for outbound message.`);let a=normalizePostInput(e),o=await sendDiscordChannelMessage({apiBaseUrl:t?.apiBaseUrl,body:a,credentials:r,fetch:t?.fetch,channelId:i});return anchor(o),o}async function editOriginal(e){let i=n.interactionToken??``;if(!i)throw Error(`discordChannel: missing interaction token for original response edit.`);let a=normalizePostInput(e),o=await editDiscordOriginalResponse({apiBaseUrl:t?.apiBaseUrl,body:a,credentials:r,fetch:t?.fetch,interactionToken:i});return n.initialResponseSent=!0,anchor(o),o}async function followup(e){let i=n.interactionToken??``;if(!i)throw Error(`discordChannel: missing interaction token for followup message.`);let a=normalizePostInput(e),o=await createDiscordFollowupMessage({apiBaseUrl:t?.apiBaseUrl,body:a,credentials:r,fetch:t?.fetch,interactionToken:i});return anchor(o),o}async function startTyping(){let e=n.channelId??``;if(e)try{await triggerDiscordTypingIndicator({apiBaseUrl:t?.apiBaseUrl,credentials:r,fetch:t?.fetch,channelId:e})}catch(t){logError(log,`Discord typing indicator failed — swallowed`,t,{channelId:e})}}return{applicationId:n.applicationId??void 0,channelId:n.channelId??``,conversationId:n.conversationId??``,guildId:n.guildId??void 0,interactionToken:n.interactionToken??void 0,request(e,n,i){return callDiscordApi({apiBaseUrl:t?.apiBaseUrl,body:n,botToken:i?.botAuth===!0?r.botToken:void 0,fetch:t?.fetch,method:i?.method,path:e})},async post(e){let t=expandPostBodies(normalizePostInput(e)),r;for(let e of t){let t=await postOne({body:e,editOriginal,followup,sendViaChannel,state:n});r===void 0&&(r=t)}return r??{id:``,raw:null}},editOriginalResponse:editOriginal,followup,sendChannelMessage:sendViaChannel,startTyping}}async function postOne(e){if(e.state.interactionToken&&e.state.applicationId)try{return e.state.initialResponseSent?await e.followup(e.body):await e.editOriginal(e.body)}catch(e){log.warn(`Discord interaction-token delivery failed, falling back to channel message`,{error:e})}return e.sendViaChannel(e.body)}async function handleInteraction(e){if(e.interaction.type===DISCORD_INTERACTION_TYPE.MESSAGE_COMPONENT){let t=readDiscordAuthorizationReveal(e.interaction.customId);if(t!==null)return revealAuthorization({...e,interaction:e.interaction,reveal:t})}return e.interaction.type===DISCORD_INTERACTION_TYPE.APPLICATION_COMMAND?handleCommandInteraction({config:e.config,interaction:e.interaction,onCommand:e.onCommand,from:e.from,waitUntil:e.waitUntil}):e.interaction.type===DISCORD_INTERACTION_TYPE.MESSAGE_COMPONENT?handleComponentInteraction({config:e.config,interaction:e.interaction,onAnswers:e.onAnswers,from:e.from,waitUntil:e.waitUntil}):handleModalSubmitInteraction({config:e.config,interaction:e.interaction,onAnswers:e.onAnswers,from:e.from,waitUntil:e.waitUntil})}async function handleCommandInteraction(e){let t=stateFromInteraction(e.interaction,{conversationId:e.interaction.id,hasMessageAnchor:!1,initialResponseSent:!1}),n={discord:buildDiscordHandle({config:e.config,state:t})},r;try{r=await e.onCommand(n,e.interaction)}catch(e){return log.error(`command handler failed`,{error:e}),discordJson({content:`The Discord command handler failed.`,ephemeral:!0})}return r==null?discordJson({content:`Command ignored.`,ephemeral:!0}):(e.waitUntil(dispatchCommand({interaction:e.interaction,result:r,from:e.from,state:t})),discordDeferredJson(r.ephemeral===!0))}function revealAuthorization(e){return e.waitUntil(answerRevealedAuthorization(e)),discordDeferredJson(!0)}async function answerRevealedAuthorization(e){let{interaction:t}=e,n=stateFromInteraction(t,{conversationId:t.messageId,hasMessageAnchor:!0,initialResponseSent:!1}),r=buildDiscordHandle({config:e.config,state:n}),i=null;try{i=(await e.onAnswers({defaultAuth:defaultDiscordAuth(t),discord:r},t))?.auth??null}catch(e){log.error(`Discord sign-in press authorization failed`,{error:e})}let a=await answerDiscordAuthorizationReveal({auth:i,resolveSession:()=>e.resolveSession(discordContinuationToken({channelId:t.channelId,conversationId:t.messageId})),reveal:e.reveal});try{await r.editOriginalResponse(a)}catch(e){log.error(`Discord sign-in reply failed`,{error:e})}}function handleComponentInteraction(e){if(isDiscordFreeformComponent(e.interaction.customId)){let t=readMessageContent(e.interaction.raw);return discordJsonBody(buildFreeformModalResponse({customId:e.interaction.customId,prompt:t}))}let t=deriveComponentAnswers(e.interaction);return t.length>0&&e.waitUntil(dispatchAnswers({config:e.config,onAnswers:e.onAnswers,conversationId:e.interaction.messageId,answers:t,interaction:e.interaction,from:e.from})),discordJsonBody({type:DISCORD_INTERACTION_RESPONSE_TYPE.DEFERRED_UPDATE_MESSAGE})}function handleModalSubmitInteraction(e){let t=deriveModalAnswers(e.interaction);return t.length>0&&e.waitUntil(dispatchAnswers({config:e.config,onAnswers:e.onAnswers,conversationId:e.interaction.messageId??e.interaction.id,answers:t,interaction:e.interaction,from:e.from})),discordJson({content:`Answer received.`,ephemeral:!0})}async function dispatchCommand(e){let t=discordCommandContent(commandInteractionMessage(e.interaction),e.interaction.attachments),n=formatDiscordContextBlock({applicationId:e.interaction.applicationId,channelId:e.interaction.channelId,commandName:e.interaction.commandName,guildId:e.interaction.guildId,interactionId:e.interaction.id,userId:e.interaction.user.id,username:e.interaction.user.username}),r=e.result.context??[];try{await e.from(discordContinuationToken({channelId:e.interaction.channelId,conversationId:e.interaction.id})).send(t,{auth:e.result.auth,context:[n,...r],history:e.result.history,state:e.state,title:e.result.title})}catch(e){log.error(`command delivery failed`,{error:e})}}async function dispatchAnswers(e){let t=stateFromInteraction(e.interaction,{conversationId:e.conversationId,hasMessageAnchor:e.conversationId!==e.interaction.id,initialResponseSent:!0}),n=buildDiscordHandle({config:e.config,state:t}),r;try{r=await e.onAnswers({defaultAuth:defaultDiscordAuth(e.interaction),discord:n},e.interaction)}catch(e){log.error(`input response handler failed`,{error:e});return}if(r===null)return;let{auth:i}=r,a=e.from(discordContinuationToken({channelId:e.interaction.channelId,conversationId:e.conversationId})),o=await respondOrRetireInactivePrompt(()=>a.respond(e.answers,{auth:i}),{retirePrompt:async t=>{let r=e.interaction.messageId,i=readMessageContent(e.interaction.raw);r!==void 0&&i!==void 0&&await editDiscordResolvedPrompt(n,{content:i,messageId:r},t)}},log);o.outcome===`inactive`&&await Promise.all([o.retirement,sendInactiveNotice(n)])}async function sendInactiveNotice(e){try{await e.followup({content:INACTIVE_REQUEST_NOTICE,flags:DISCORD_EPHEMERAL_MESSAGE_FLAG})}catch(e){log.error(`inactive prompt notice failed`,{error:e})}}function acceptAsPresser(e){return{auth:e.defaultAuth}}function dropUnmappedAnswer(){return log.warn(`dropped a Discord input response: onCommand is customized but onAnswers is not; set onAnswers to accept button presses and modal submissions`),null}function stateFromInteraction(e,t){return{audience:discordAudience(e.channelType),applicationId:e.applicationId,...e.channelType!==void 0&&{channelType:e.channelType},channelId:e.channelId,conversationId:t.conversationId,guildId:e.guildId??null,hasMessageAnchor:t.hasMessageAnchor,initialResponseSent:t.initialResponseSent,interactionToken:e.token}}function initialDiscordState(){return{applicationId:null,channelId:null,conversationId:null,guildId:null,hasMessageAnchor:!1,initialResponseSent:!1,interactionToken:null}}function mergeCredentials(e,t){return{applicationId:t.applicationId??e?.applicationId,botToken:e?.botToken??(()=>resolveDiscordBotToken()),publicKey:e?.publicKey,webhookVerifier:e?.webhookVerifier}}function normalizePostInput(e){return typeof e==`string`?{content:e}:e}function expandPostBodies(e){return typeof e.content==`string`?splitDiscordMessageContent(e.content).map((t,n)=>n===0?{...e,content:t}:{allowed_mentions:e.allowed_mentions,content:t}):[e]}export{discordChannel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{readNonEmptyString}from"#shared/guards.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{createGitHubBotNameResolver}from"#public/channels/github/auth.js";import{getGitHubRepository}from"#public/channels/github/api.js";import{parseGitHubWebhookEvent}from"#public/channels/github/inbound.js";import{buildGitHubBinding}from"#public/channels/github/binding.js";import{continuationTokenFromState,conversationFromState,initialGitHubState,stateFromReceiveTarget}from"#public/channels/github/state.js";import{GITHUB_CHANNEL_DEFAULT_ROUTE}from"#public/channels/github/constants.js";import{createDefaultEvents,defaultOnComment}from"#public/channels/github/defaults.js";import{dispatchCheckRun,dispatchCheckSuite,dispatchIssue,dispatchIssueComment,dispatchPullRequest,dispatchPullRequestReviewComment,dispatchWorkflowRun}from"#public/channels/github/dispatch.js";import{verifyGitHubRequest}from"#public/channels/github/verify.js";const log=createLogger(`github.channel`);function githubChannel(e={}){let t=createGitHubBotNameResolver({botName:e.botName,credentials:e.credentials}),n={botName:t},r={...createDefaultEvents({api:e.api,botName:t,credentials:e.credentials,progress:e.progress}),...e.events};return defineChannel({turnPolicy:e.turnPolicy,state:initialGitHubState(),context(t,n){return rebuildGitHubContext(t,n,e)},routes:[POST(e.route??GITHUB_CHANNEL_DEFAULT_ROUTE,async(r,{from:i,waitUntil:a})=>{let o=await verifyInbound(r,e.credentials);if(o===null)return new Response(`unauthorized`,{status:401});let s=missingGitHubWebhookHeaders(r.headers),c;try{c=parseGitHubWebhookEvent({body:o,contentType:r.headers.get(`content-type`)??void 0,headers:r.headers})}catch(e){return log.warn(`inbound GitHub body is not valid JSON`,{error:e}),jsonOk({ignored:!0,ok:!0})}return c===null?(s.length>0&&log.warn(`GitHub webhook ignored because standard headers were missing`,{missingHeaders:s}),jsonOk({ignored:!0,ok:!0})):(s.length>0&&log.warn(`GitHub webhook missing standard headers; inferred metadata from payload`,{deliveryId:c.delivery.id,event:c.delivery.event,missingHeaders:s,repository:c.repository.fullName}),c.kind===`ping`?jsonOk({ok:!0}):c.kind===`issue_comment`&&c.action===`created`?(a(dispatchIssueComment({botName:t,config:e,event:c,handler:e.onComment??((e,t)=>defaultOnComment(e,t,n)),from:i})),jsonOk({ok:!0})):c.kind===`pull_request_review_comment`&&c.action===`created`?(a(dispatchPullRequestReviewComment({botName:t,config:e,event:c,handler:e.onComment??((e,t)=>defaultOnComment(e,t,n)),from:i})),jsonOk({ok:!0})):c.kind===`issues`&&e.onIssue!==void 0?(a(dispatchIssue({botName:t,config:e,event:c,handler:e.onIssue,from:i})),jsonOk({ok:!0})):c.kind===`pull_request`&&e.onPullRequest!==void 0?(a(dispatchPullRequest({botName:t,config:e,event:c,handler:e.onPullRequest,from:i})),jsonOk({ok:!0})):c.kind===`check_suite`&&e.onCheckSuite!==void 0?(a(dispatchCheckSuite({botName:t,config:e,event:c,handler:e.onCheckSuite,from:i})),jsonOk({ok:!0})):c.kind===`check_run`&&e.onCheckRun!==void 0?(a(dispatchCheckRun({botName:t,config:e,event:c,handler:e.onCheckRun,from:i})),jsonOk({ok:!0})):c.kind===`workflow_run`&&e.onWorkflowRun!==void 0?(a(dispatchWorkflowRun({botName:t,config:e,event:c,handler:e.onWorkflowRun,from:i})),jsonOk({ok:!0})):jsonOk({ignored:!0,ok:!0}))})],async receive(t,{from:
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{readNonEmptyString}from"#shared/guards.js";import{POST,defineChannel}from"#public/definitions/channel.js";import{createGitHubBotNameResolver}from"#public/channels/github/auth.js";import{getGitHubRepository}from"#public/channels/github/api.js";import{parseGitHubWebhookEvent}from"#public/channels/github/inbound.js";import{buildGitHubBinding}from"#public/channels/github/binding.js";import{continuationTokenFromState,conversationFromState,initialGitHubState,stateFromReceiveTarget}from"#public/channels/github/state.js";import{GITHUB_CHANNEL_DEFAULT_ROUTE}from"#public/channels/github/constants.js";import{createDefaultEvents,defaultOnComment}from"#public/channels/github/defaults.js";import{dispatchCheckRun,dispatchCheckSuite,dispatchIssue,dispatchIssueComment,dispatchPullRequest,dispatchPullRequestReviewComment,dispatchWorkflowRun}from"#public/channels/github/dispatch.js";import{verifyGitHubRequest}from"#public/channels/github/verify.js";const log=createLogger(`github.channel`);function githubChannel(e={}){let t=createGitHubBotNameResolver({botName:e.botName,credentials:e.credentials}),n={botName:t},r={...createDefaultEvents({api:e.api,botName:t,credentials:e.credentials,progress:e.progress}),...e.events};return defineChannel({turnPolicy:e.turnPolicy,state:initialGitHubState(),context(t,n){return rebuildGitHubContext(t,n,e)},routes:[POST(e.route??GITHUB_CHANNEL_DEFAULT_ROUTE,async(r,{from:i,waitUntil:a})=>{let o=await verifyInbound(r,e.credentials);if(o===null)return new Response(`unauthorized`,{status:401});let s=missingGitHubWebhookHeaders(r.headers),c;try{c=parseGitHubWebhookEvent({body:o,contentType:r.headers.get(`content-type`)??void 0,headers:r.headers})}catch(e){return log.warn(`inbound GitHub body is not valid JSON`,{error:e}),jsonOk({ignored:!0,ok:!0})}return c===null?(s.length>0&&log.warn(`GitHub webhook ignored because standard headers were missing`,{missingHeaders:s}),jsonOk({ignored:!0,ok:!0})):(s.length>0&&log.warn(`GitHub webhook missing standard headers; inferred metadata from payload`,{deliveryId:c.delivery.id,event:c.delivery.event,missingHeaders:s,repository:c.repository.fullName}),c.kind===`ping`?jsonOk({ok:!0}):c.kind===`issue_comment`&&c.action===`created`?(a(dispatchIssueComment({botName:t,config:e,event:c,handler:e.onComment??((e,t)=>defaultOnComment(e,t,n)),from:i})),jsonOk({ok:!0})):c.kind===`pull_request_review_comment`&&c.action===`created`?(a(dispatchPullRequestReviewComment({botName:t,config:e,event:c,handler:e.onComment??((e,t)=>defaultOnComment(e,t,n)),from:i})),jsonOk({ok:!0})):c.kind===`issues`&&e.onIssue!==void 0?(a(dispatchIssue({botName:t,config:e,event:c,handler:e.onIssue,from:i})),jsonOk({ok:!0})):c.kind===`pull_request`&&e.onPullRequest!==void 0?(a(dispatchPullRequest({botName:t,config:e,event:c,handler:e.onPullRequest,from:i})),jsonOk({ok:!0})):c.kind===`check_suite`&&e.onCheckSuite!==void 0?(a(dispatchCheckSuite({botName:t,config:e,event:c,handler:e.onCheckSuite,from:i})),jsonOk({ok:!0})):c.kind===`check_run`&&e.onCheckRun!==void 0?(a(dispatchCheckRun({botName:t,config:e,event:c,handler:e.onCheckRun,from:i})),jsonOk({ok:!0})):c.kind===`workflow_run`&&e.onWorkflowRun!==void 0?(a(dispatchWorkflowRun({botName:t,config:e,event:c,handler:e.onWorkflowRun,from:i})),jsonOk({ok:!0})):jsonOk({ignored:!0,ok:!0}))})],async receive({message:t,target:n,...r},{from:i}){let a=n,o=readNonEmptyString(a.owner),s=readNonEmptyString(a.repo);if(o===void 0||s===void 0)throw Error(`githubChannel().receive requires target.owner and target.repo.`);if([a.issueNumber!==void 0,a.pullRequestNumber!==void 0].filter(Boolean).length!==1)throw Error(`githubChannel().receive requires exactly one of issueNumber or pullRequestNumber.`);let c=a.repositoryId??(await getGitHubRepository({api:e.api,credentials:e.credentials,installationId:a.installationId,owner:o,repo:s})).id,l=stateFromReceiveTarget({target:a,owner:o,repo:s,repositoryId:c});if(a.initialMessage!==void 0){let{thread:t}=buildGitHubBinding({config:e,state:l});await t.post(a.initialMessage)}return i(continuationTokenFromState(l)).send(t,{...r,state:l})},events:r})}function rebuildGitHubContext(e,t,n){let r=buildGitHubBinding({config:n,state:e});return{conversation:conversationFromState(e),github:r.github,repository:r.github.repository,state:e,thread:r.thread}}async function verifyInbound(e,t){try{return await verifyGitHubRequest(e,{webhookSecret:t?.webhookSecret,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`github inbound verification failed`,{error:e}),null}}function missingGitHubWebhookHeaders(e){return[`x-github-event`,`x-github-delivery`].filter(t=>{let n=e.get(t);return n===null||n.trim().length===0})}function jsonOk(e){return new Response(JSON.stringify(e),{headers:{"content-type":`application/json; charset=utf-8`},status:200})}export{githubChannel};
|
|
@@ -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{createDefaultEvents,defaultOnAgentSession}from"#public/channels/linear/defaults.js";import{formatLinearContextBlock,linearContinuationToken,messageFromLinearAgentSessionEvent,parseLinearWebhookEvent}from"#public/channels/linear/inbound.js";import{LINEAR_CHANNEL_DEFAULT_ROUTE}from"#public/channels/linear/constants.js";import{attachLinearInboundUploads}from"#public/channels/linear/inbound-uploads.js";import{verifyLinearRequest}from"#public/channels/linear/verify.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({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:
|
|
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{createDefaultEvents,defaultOnAgentSession}from"#public/channels/linear/defaults.js";import{formatLinearContextBlock,linearContinuationToken,messageFromLinearAgentSessionEvent,parseLinearWebhookEvent}from"#public/channels/linear/inbound.js";import{LINEAR_CHANNEL_DEFAULT_ROUTE}from"#public/channels/linear/constants.js";import{attachLinearInboundUploads}from"#public/channels/linear/inbound-uploads.js";import{verifyLinearRequest}from"#public/channels/linear/verify.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({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({message:t,target:n,...r},{from:i}){let a=n,o=await resolveReceiveSession(a,e),s=readNonEmptyString(a.initialActivity);return s!==void 0&&await createLinearAgentActivity({api:e.api,credentials:e.credentials,activity:{agentSessionId:o.id,content:{body:s,type:`thought`}}}),i(linearContinuationToken({agentSessionId:o.id})).send(t,{...r,state:stateFromAgentSession(o)})},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({agentSessionId: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 +1 @@
|
|
|
1
|
-
import{DEFAULT_UPLOAD_POLICY}from"#public/channels/upload-policy.js";import{
|
|
1
|
+
import{DEFAULT_UPLOAD_POLICY}from"#public/channels/upload-policy.js";import{ResponseTooLargeError}from"#execution/public-destination.js";import{requestPublicUrl}from"#execution/web-fetch/request.js";import{overLimit}from"#internal/attachments/limited-read.js";import{fetchMessageBestEffort}from"#public/channels/chat-sdk/fetch-message.js";import{messageContentOf}from"#public/channels/chat-sdk/index.js";function linqInboundContent(e){let t=messageContentOf(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(){try{let e=await requestPublicUrl(t,{headers:{},maxResponseSize:DEFAULT_UPLOAD_POLICY.maxBytes,signal:AbortSignal.timeout(3e4)});if(!e.ok)throw Error(`Linq CDN returned HTTP ${e.status}.`);return Buffer.from(await e.arrayBuffer())}catch(e){throw e instanceof ResponseTooLargeError?overLimit(DEFAULT_UPLOAD_POLICY.maxBytes,`linq`):e}}}:e}export{linqInboundContent,linqQuotedMessage,rehydrateLinqAttachment};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { SlackChannelConfig, SlackAnswerResult, SlackAnswerSubmission } from "#public/channels/slack/slackChannel.js";
|
|
1
|
+
import type { SlackChannelConfig, SlackAnswerResult, SlackAnswerSubmission, SlackSessionTarget } from "#public/channels/slack/slackChannel.js";
|
|
2
2
|
import type { SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
3
3
|
export declare function authorizeAnswer(input: {
|
|
4
|
-
readonly channelId: string;
|
|
5
4
|
readonly deps: {
|
|
6
5
|
readonly api: SlackTransportOptions | undefined;
|
|
7
6
|
readonly config: SlackChannelConfig;
|
|
@@ -9,6 +8,6 @@ export declare function authorizeAnswer(input: {
|
|
|
9
8
|
};
|
|
10
9
|
readonly installationTeamId: string | null | undefined;
|
|
11
10
|
readonly submission: SlackAnswerSubmission;
|
|
11
|
+
readonly target: SlackSessionTarget;
|
|
12
12
|
readonly teamId: string | null | undefined;
|
|
13
|
-
readonly threadTs: string;
|
|
14
13
|
}): Promise<SlackAnswerResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{buildSlackBinding}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";const log=createLogger(`slack.interactions`);async function authorizeAnswer(e){let
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{buildSlackBinding}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";const log=createLogger(`slack.interactions`);async function authorizeAnswer(e){let{channelId:t,codeChannel:n,threadTs:r=``}=e.target,i=buildSlackAuthContext({channelId:t,installationTeamId:e.installationTeamId,teamId:e.teamId,threadTs:r,userId:e.submission.user.id,userName:e.submission.user.username??e.submission.user.name}),{thread:a,slack:o}=buildSlackBinding({api:e.deps.api,botToken:e.deps.config.credentials?.botToken,channelId:t,threadTs:r,codeChannel:n,installationTeamId:e.installationTeamId??void 0,teamId:e.teamId??void 0}),s={defaultAuth:i,slack:o,thread:a};try{return await e.deps.onAnswers(s,e.submission)}catch(e){return log.error(`HITL input response authorization failed`,{error:e}),null}}export{authorizeAnswer};
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { type SlackApiResponse as SlackPrimitiveApiResponse } from "#compiled/@chat-adapter/slack/api.js";
|
|
18
18
|
import { type CardElement, type FileUpload } from "#public/channels/slack/cards.js";
|
|
19
19
|
import { type SlackTransportOptions } from "#public/channels/slack/transport.js";
|
|
20
|
+
import type { SlackSessionTarget } from "#public/channels/slack/slackChannel.js";
|
|
20
21
|
/** Slack app installation workspace available when eve resolves a bot token. */
|
|
21
22
|
export interface SlackBotTokenContext {
|
|
22
23
|
readonly teamId?: string;
|
|
@@ -37,6 +38,15 @@ export declare function slackContinuationToken({ channelId, threadTs, }: {
|
|
|
37
38
|
readonly channelId: string;
|
|
38
39
|
readonly threadTs: string;
|
|
39
40
|
}): string;
|
|
41
|
+
/**
|
|
42
|
+
* Builds the channel-local continuation token of a code channel's one
|
|
43
|
+
* session (`code:<channelId>`), which no thread token can collide with.
|
|
44
|
+
*/
|
|
45
|
+
export declare function slackCodeChannelContinuationToken(channelId: string): string;
|
|
46
|
+
/** The session `slack` is bound to: its code channel, or its thread. */
|
|
47
|
+
export declare function slackSessionTargetOf(slack: Pick<SlackHandle, "channelId" | "codeChannel" | "threadTs">): SlackSessionTarget;
|
|
48
|
+
/** The continuation token of a thread's or a code channel's session. */
|
|
49
|
+
export declare function slackSessionToken(target: SlackSessionTarget): string;
|
|
40
50
|
/**
|
|
41
51
|
* Materializes a {@link SlackBotToken} to a string, falling back to
|
|
42
52
|
* `process.env.SLACK_BOT_TOKEN`. Throws when neither is set.
|
|
@@ -224,8 +234,9 @@ export interface SlackThread {
|
|
|
224
234
|
/**
|
|
225
235
|
* Show a typing/status indicator in this thread via Slack's
|
|
226
236
|
* `assistant.threads.setStatus`. Called with no argument, clears the
|
|
227
|
-
* indicator (empty status).
|
|
228
|
-
*
|
|
237
|
+
* indicator (empty status). In a code channel, sets the session status to
|
|
238
|
+
* `processing`, or `active` when cleared. Failures are logged and swallowed:
|
|
239
|
+
* the indicator is a UX nicety, never a reason to fail a turn.
|
|
229
240
|
*/
|
|
230
241
|
startTyping(status?: string): Promise<void>;
|
|
231
242
|
/**
|
|
@@ -251,8 +262,13 @@ export interface SlackThread {
|
|
|
251
262
|
export interface SlackHandle {
|
|
252
263
|
/** Slack channel id. */
|
|
253
264
|
readonly channelId: string;
|
|
254
|
-
/**
|
|
265
|
+
/**
|
|
266
|
+
* Slack thread root ts (or the message ts when not in a thread). Empty in a
|
|
267
|
+
* code channel, which is one session across its threads.
|
|
268
|
+
*/
|
|
255
269
|
readonly threadTs: string;
|
|
270
|
+
/** Whether the session serves a Slack code channel. */
|
|
271
|
+
readonly codeChannel: boolean;
|
|
256
272
|
/** Slack team id, when the inbound event carried one. */
|
|
257
273
|
readonly teamId: string | undefined;
|
|
258
274
|
/**
|
|
@@ -339,6 +355,10 @@ export declare function buildSlackBinding(input: {
|
|
|
339
355
|
readonly botUserId?: string;
|
|
340
356
|
readonly channelId: string;
|
|
341
357
|
readonly threadTs: string;
|
|
358
|
+
/** Binds the code channel `channelId` as its one session; `threadTs` is ignored. */
|
|
359
|
+
readonly codeChannel?: boolean;
|
|
360
|
+
/** In a code channel, the thread replies post in instead of top-level. */
|
|
361
|
+
readonly replyThreadTs?: () => string | undefined;
|
|
342
362
|
/**
|
|
343
363
|
* Whether `threadTs` already has replies; Slack hides threaded
|
|
344
364
|
* ephemerals until it does. Defaults to true when `threadTs` is set.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{isCardElement}from"#public/channels/slack/cards.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({channelId:e,threadTs: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;
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{isCardElement}from"#public/channels/slack/cards.js";import{cardToBlocks,cardToFallbackText}from"#public/channels/slack/blocks.js";import{resolveSlackInboundMrkdwn}from"#public/channels/slack/inbound-content.js";import{setSlackCodeChannelStatus,slackThreadTsParam}from"#public/channels/slack/code-channel.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({channelId:e,threadTs:t}){return`${e}:${t}`}function slackCodeChannelContinuationToken(e){return`code:${e}`}function slackSessionTargetOf(e){return e.codeChannel?{channelId:e.channelId,codeChannel:!0}:{channelId:e.channelId,threadTs:e.threadTs}}function slackSessionToken(e){return e.codeChannel===!0?slackCodeChannelContinuationToken(e.channelId):slackContinuationToken({channelId:e.channelId,threadTs:e.threadTs})}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;o&&t.channel===e.channelId&&t.thread_ts===o&&e.onPosted?.()}return i},i=[],a=e.codeChannel===!0,o=a?``:e.threadTs,hasSessionThread=()=>a||o!==``,s=e.threadHasReplies??hasSessionThread(),c;function postThreadTs(){return(a?e.replyThreadTs?.():void 0)||o}function handleMessageTs(t){hasSessionThread()||t===o||(o=t,e.onThreadTsChanged?.(t))}async function uploadFiles(t,r){let i=r?.channelId??e.channelId,a=r?.threadTs??postThreadTs(),o=t.map(e=>toSlackFileUpload(e,r?.snippetType)),s=await uploadSlackFiles(o,{...n,channelId:i||void 0,initialComment:r?.initialComment,threadTs:slackThreadTsParam(a)});return i===e.channelId&&hasSessionThread()&&a===postThreadTs()&&e.onPosted?.(),s}function refreshMessages(){if(c!==void 0)return c;async function fetchAndReplace(){if(!e.channelId||!o||a){i=[];return}try{i=(await fetchSlackThreadReplies({...n,channel:e.channelId,limit:50,ts:o})).messages.map(t=>parseThreadMessage(t,o,{appId:e.appId,botUserId:e.botUserId}))}catch(t){logError(log,`refresh threw — swallowed`,t,{channelId:e.channelId})}}let t=fetchAndReplace().finally(()=>{c===t&&(c=void 0)});return c=t,t}async function fetchWindow(t,r){if(!e.channelId||!o||a)return;let i=[],s;for(let a=0;a<5;a+=1){let a=await fetchSlackThreadReplies({...n,channel:e.channelId,cursor:s,inclusive:!0,latest:t,limit:200,ts:o});if(i=[...i,...a.messages].slice(-r),s=a.nextCursor,s===void 0||s===``)return i.map(t=>parseThreadMessage(t,o,{appId:e.appId,botUserId:e.botUserId}))}}let l={get recentMessages(){return i},async listParticipants(){await l.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??[],o=`blocks`in r||`card`in r||`markdown`in r;if(i.length>0&&!o){let e=await uploadFiles(i,{initialComment:`text`in r?r.text:void 0});return hasSessionThread()&&(s=!0),{id:Array.isArray(e.raw.files)&&e.raw.files.length>0?String(e.raw.files[0].id??``):``,raw:e.raw}}let c=postThreadTs(),l=await postSlackMessage(buildPostMessageOptions(r,e.channelId,postThreadTs(),n)),u=l.id;if((c||a)&&e.onPosted?.(),hasSessionThread()&&(s=!0),handleMessageTs(u),i.length>0&&o)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),a=s?postThreadTs():``,o=await postSlackEphemeral({...buildPostMessageOptions(i,e.channelId,a,n),user:t});return{id:o.id,raw:o.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){if(a){await setSlackCodeChannelStatus(request,e.channelId,t?`processing`:`active`);return}if(o)try{let n=t===void 0?``:truncateTypingStatus(t),r={channel_id:e.channelId,thread_ts:o,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}>`}},u={channelId:e.channelId,get threadTs(){return o},codeChannel:a,teamId:e.teamId,request,uploadFiles};return threadWindowFetchers.set(l,fetchWindow),{thread:l,slack:u}}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:slackThreadTsParam(n),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,slackCodeChannelContinuationToken,slackContinuationToken,slackSessionTargetOf,slackSessionToken};
|
|
@@ -4,5 +4,7 @@ interface SlackAppManifestBuildDefinition {
|
|
|
4
4
|
}
|
|
5
5
|
export declare function defineSlackAppManifest(input: {
|
|
6
6
|
readonly botName?: string;
|
|
7
|
+
/** Adds the scopes, events, and feature flag Slack code channels need. */
|
|
8
|
+
readonly codeChannels?: boolean;
|
|
7
9
|
}): SlackAppManifestBuildDefinition;
|
|
8
10
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function defineSlackAppManifest(e){return{build(t){let n=(e.botName??t).slice(0,35)
|
|
1
|
+
function defineSlackAppManifest(e){return{build(t){let n=(e.botName??t).slice(0,35),r={$type:`https://docs.slack.dev/reference/app-manifest/`,display_information:{name:n},features:{app_home:{home_tab_enabled:!1,messages_tab_enabled:!0,messages_tab_read_only_enabled:!1},bot_user:{display_name:n}},oauth_config:{scopes:{bot:[`app_mentions:read`,`chat:write`]}},settings:{event_subscriptions:{bot_events:[`app_mention`]},org_deploy_enabled:!1,socket_mode_enabled:!1,token_rotation_enabled:!1}};return e.codeChannels===!0?withCodeChannels(r):r}}}function withCodeChannels(e){return{...e,features:{...e.features,code_channels:{enabled:!0}},oauth_config:{scopes:{bot:[...e.oauth_config.scopes.bot,`channels:history`,`channels:read`,`code_channels:manage`,`groups:history`,`groups:read`,`im:read`,`mpim:read`]}},settings:{...e.settings,event_subscriptions:{bot_events:[...e.settings.event_subscriptions.bot_events,`agent_session_stopped`,`message.session`]}}}}export{defineSlackAppManifest};
|
|
@@ -7,7 +7,8 @@ interface SlackAuthContextInput {
|
|
|
7
7
|
readonly installationTeamId?: string | null;
|
|
8
8
|
readonly isBot?: boolean;
|
|
9
9
|
readonly teamId?: string | null;
|
|
10
|
-
|
|
10
|
+
/** Thread root ts; omit it in a code channel, which is one session across its threads. */
|
|
11
|
+
readonly threadTs?: string;
|
|
11
12
|
readonly userId: string;
|
|
12
13
|
readonly userName?: string;
|
|
13
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function slackUserIdFromAuthContext(e){if(e?.authenticator!==`slack-webhook`)return;let t=e.attributes.user_id;return typeof t==`string`&&t.length>0?t:void 0}function slackUserIdForPrincipal(e,t){return t===void 0?void 0:e.slackUsersByPrincipal?.[t]}function buildSlackAuthContext(e){let t=e.isBot===!0,n=e.installationTeamId||e.teamId,r=n?t?`slack:${n}:bot:${e.userId}`:`slack:${n}:${e.userId}`:t?`slack:bot:${e.userId}`:`slack:${e.userId}`,i={author_type:t?`bot`:`user`,channel_id:e.channelId,
|
|
1
|
+
function slackUserIdFromAuthContext(e){if(e?.authenticator!==`slack-webhook`)return;let t=e.attributes.user_id;return typeof t==`string`&&t.length>0?t:void 0}function slackUserIdForPrincipal(e,t){return t===void 0?void 0:e.slackUsersByPrincipal?.[t]}function buildSlackAuthContext(e){let t=e.isBot===!0,n=e.installationTeamId||e.teamId,r=n?t?`slack:${n}:bot:${e.userId}`:`slack:${n}:${e.userId}`:t?`slack:bot:${e.userId}`:`slack:${e.userId}`,i={author_type:t?`bot`:`user`,channel_id:e.channelId,user_id:e.userId};return e.threadTs&&(i.thread_ts=e.threadTs),e.userName&&(i.user_name=e.userName),e.fullName&&(i.full_name=e.fullName),e.teamId&&(i.team_id=e.teamId),{attributes:i,authenticator:`slack-webhook`,issuer:n?`slack:${n}`:`slack`,principalId:r,principalType:t?`service`:`user`}}export{buildSlackAuthContext,slackUserIdForPrincipal,slackUserIdFromAuthContext};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { SlackApiResponse } from "#public/channels/slack/api.js";
|
|
2
|
+
export type SlackRequest = (operation: string, body: unknown) => Promise<SlackApiResponse>;
|
|
3
|
+
/**
|
|
4
|
+
* Whether the conversation is a code channel. A failed lookup counts as not a
|
|
5
|
+
* code channel, so its messages get per-thread sessions until a retry
|
|
6
|
+
* succeeds a minute later.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isSlackCodeChannel(channelId: string, request: SlackRequest): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* Opens a code channel from an origin message. Slack invites the origin
|
|
11
|
+
* message's author, links the channel in the origin thread, and refuses Slack
|
|
12
|
+
* Connect origins. `session_id` makes the call idempotent, so a retry with the
|
|
13
|
+
* same origin and requester returns the same channel. Throws an error marked
|
|
14
|
+
* `fatal` when Slack refuses permanently, so the calling step doesn't retry.
|
|
15
|
+
*
|
|
16
|
+
* eve reads the origin before creating anything and asks for a private
|
|
17
|
+
* channel when the origin is private. Slack always honors an explicit
|
|
18
|
+
* `is_private`, so the check after creation is a backstop: on a mismatch eve
|
|
19
|
+
* archives the channel and throws.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createSlackCodeChannel(input: {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly originChannelId: string;
|
|
24
|
+
readonly originTs: string;
|
|
25
|
+
/** Principal the channel is opened for; one channel per requester and origin message. */
|
|
26
|
+
readonly requester: string;
|
|
27
|
+
readonly request: SlackRequest;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
readonly channelId: string;
|
|
30
|
+
readonly isPrivate: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Whether a message is the origin quote Slack posts when it creates a code
|
|
34
|
+
* channel from a message. It is context, not a request.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isSlackCodeChannelOriginContext(raw: Record<string, unknown>): boolean;
|
|
37
|
+
/** Slack agent session lifecycle states. */
|
|
38
|
+
export type SlackSessionStatus = "active" | "processing" | "suspended";
|
|
39
|
+
/** Sets a code channel's session status. Failures are logged, never thrown. */
|
|
40
|
+
export declare function setSlackCodeChannelStatus(request: SlackRequest, channelId: string, status: SlackSessionStatus): Promise<void>;
|
|
41
|
+
/** Slack's `thread_ts` for a session thread: omitted for top-level posts. */
|
|
42
|
+
export declare function slackThreadTsParam(threadTs: string): string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash}from"node:crypto";import{createLogger,logError}from"#internal/logging.js";const log=createLogger(`slack.code-channel`),codeChannelCache=new Map;async function isSlackCodeChannel(e,t){let n=codeChannelCache.get(e);if(typeof n==`boolean`)return n;if(n!==void 0&&Date.now()-n.failedAtMs<6e4)return!1;let r;try{let n=await t(`conversations.info`,{channel:e});if(n.ok!==!0)return log.error(n.error===`missing_scope`?`conversations.info needs the channels:read and groups:read scopes to detect code channels; reinstall the Slack app`:`conversations.info failed; treating the channel as an ordinary channel`,{channelId:e,error:n.error}),cacheLookup(e,{failedAtMs:Date.now()}),!1;r=n.channel?.properties?.record_channel?.record_type===`agent_channel`}catch(t){return logError(log,`conversations.info threw; treating the channel as an ordinary channel`,t,{channelId:e}),cacheLookup(e,{failedAtMs:Date.now()}),!1}return cacheLookup(e,r),r}function cacheLookup(e,t){codeChannelCache.delete(e),codeChannelCache.size>=1e3&&codeChannelCache.delete(codeChannelCache.keys().next().value),codeChannelCache.set(e,t)}const TRANSIENT_SLACK_ERRORS=new Set([`channel_creation_failed`,`fatal_error`,`internal_error`,`invite_failed`,`open_failed`,`ratelimited`,`request_timeout`,`service_unavailable`]);async function createSlackCodeChannel(e){let t=createHash(`sha256`).update(`${e.originChannelId}:${e.originTs}:${e.requester}`).digest(`hex`).slice(0,32),n=e.name.replace(/\s+/gu,` `).trim().slice(0,80),r=isPrivateConversation(await readConversation(e.request,e.originChannelId)),i=await e.request(`agents.conversations.create`,{...n.length>0&&{name:n},...r&&{is_private:!0},origin_channel_id:e.originChannelId,origin_message_ts:e.originTs,session_id:t});if(i.ok!==!0)throw slackFailure(`agents.conversations.create`,i.error);let a=i.channel_id;if(typeof a!=`string`||a.length===0)throw Error(`Slack did not return a channel id for the new code channel.`);if(cacheLookup(a,!0),r&&!isPrivateConversation(await readConversation(e.request,a)))throw fatalError(`Slack opened code channel ${a} as public from a private conversation, so eve ${(await e.request(`agents.conversations.archive`,{channel_id:a})).ok===!0?`archived it`:`couldn't archive it`} and didn't copy the conversation into it.`);return{channelId:a,isPrivate:r}}async function readConversation(e,t){let n=await e(`conversations.info`,{channel:t});if(n.ok!==!0)throw slackFailure(`conversations.info`,n.error);return n.channel??{}}function isPrivateConversation(e){return e.is_private===!0||e.is_im===!0||e.is_mpim===!0}function slackFailure(e,t){let n=typeof t==`string`?t:`unknown_error`,r=`Slack ${e} failed: ${n}`;return TRANSIENT_SLACK_ERRORS.has(n)?Error(r):fatalError(r)}function fatalError(e){return Object.assign(Error(e),{fatal:!0})}function isSlackCodeChannelOriginContext(e){let t=e.blocks;return Array.isArray(t)&&t.some(e=>e?.block_id===`agent_channel_origin_context`)}async function setSlackCodeChannelStatus(e,t,n){try{let r=await e(`agents.sessions.setStatus`,{channel_id:t,status:n});r.ok!==!0&&log.warn(`agents.sessions.setStatus returned not-ok`,{error:r.error,status:n})}catch(e){logError(log,`agents.sessions.setStatus threw`,e,{channelId:t})}}function slackThreadTsParam(e){return e===``?void 0:e}export{createSlackCodeChannel,isSlackCodeChannel,isSlackCodeChannelOriginContext,setSlackCodeChannelStatus,slackThreadTsParam};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
2
2
|
import type { SlackMessage } from "#public/channels/slack/inbound.js";
|
|
3
|
+
import type { SlackInteractionCardChain } from "#public/channels/slack/renderers.js";
|
|
3
4
|
import { type SignIn } from "#channel/sign-in.js";
|
|
4
5
|
import type { SlackApprovalChannelResolver, SlackChannelInternalEvents, SlackContext, SlackEventContext, SlackMentionResult } from "#public/channels/slack/slackChannel.js";
|
|
5
6
|
/**
|
|
@@ -24,10 +25,13 @@ export declare function defaultReceived(_message: SlackMessage, ctx: SlackContex
|
|
|
24
25
|
*/
|
|
25
26
|
export declare function showSlackSignIn(event: SignIn, channel: SlackEventContext): Promise<void>;
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
-
* as
|
|
28
|
+
* eve's default handlers for input requests, rendering cards through the composed
|
|
29
|
+
* `interactionCard` chain. A commit's requests post as one batch on its first
|
|
30
|
+
* `interaction.opened`. Each card shows an open request's answer when it is admitted or refused,
|
|
31
|
+
* and its settled rendering on `interaction.settled`. A sign-in renders as its public status and
|
|
32
|
+
* private challenge instead.
|
|
29
33
|
*/
|
|
30
|
-
export declare function
|
|
34
|
+
export declare function defaultInteractionEvents(approvalChannel: SlackApprovalChannelResolver | undefined, interactionCard: SlackInteractionCardChain): Pick<SlackChannelInternalEvents, "interaction.opened" | "interaction.settled" | "response.admitted" | "response.settled">;
|
|
31
35
|
/**
|
|
32
36
|
* eve's default Slack event rendering: status lines, replies, errors, and the
|
|
33
37
|
* connection-authorization flow. It is the innermost link of every channel's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{contentPhase}from"#protocol/session-events/catalog.js";import{callLabel,waiting}from"#protocol/session-projection/selectors.js";import{createLogger,formatErrorHint}from"#internal/logging.js";import{signInOf}from"#channel/sign-in.js";import{refusedAnswerOf,requestBatchOf
|
|
1
|
+
import{contentPhase}from"#protocol/session-events/catalog.js";import{callLabel,waiting}from"#protocol/session-projection/selectors.js";import{createLogger,formatErrorHint,logError}from"#internal/logging.js";import{signInOf}from"#channel/sign-in.js";import{refusedAnswerOf,requestBatchOf}from"#channel/interaction-prompts.js";import{sessionTables}from"#protocol/session-projection/session-view.js";import{errorHintOf}from"#public/channels/reply.js";import{slackSessionTargetOf}from"#public/channels/slack/api.js";import{buildSlackAuthContext,slackUserIdForPrincipal}from"#public/channels/slack/auth.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{workingTaskNames}from"#channel/task-card.js";import{reviewingResults,waitingOnTasks,withMoreCalls}from"#public/channels/slack/action-status.js";import{buildAuthCompletedText,buildAuthEphemeralBlocks,buildAuthEphemeralText,buildAuthRequiredPublicText}from"#public/channels/slack/connections.js";import{notifyRefusedResponder,postRequestCards,settleRequestCard,showAnswerChange}from"#public/channels/slack/request-cards.js";import{deliverCompletedSlackReply}from"#public/channels/slack/reply-delivery.js";import{clearStatus,hideStatus,reasoningStatus,showStatus}from"#public/channels/slack/thread-status.js";const log=createLogger(`slack.defaults`),streamByState=new WeakMap;function stepStream(e,t){let n=streamByState.get(e);if(n?.runId===t)return n;let r={calls:null,partKinds:new Map,reasoning:``,reasoningShownAtMs:null,replyChars:0,runId:t};return streamByState.set(e,r),r}function deltaStream(e,t,n){if(n!==void 0&&t.kind!==void 0){let r=stepStream(e,n);return r.partKinds.set(t.partId,t.kind),{kind:t.kind,stream:r}}let r=streamByState.get(e),i=r?.partKinds.get(t.partId);return r===void 0||i===void 0?void 0:{kind:i,stream:r}}function heldWithin(e,t){return e!=null&&t-e>=0&&t-e<3e3}async function showReasoning(e,t,n){let r=reasoningStatus(t.reasoning,n);if(r===void 0||truncateTypingStatus(r)===e.state.threadStatus?.text)return;let i=Date.now();heldWithin(t.reasoningShownAtMs,i)||(await showStatus(e,r),t.reasoningShownAtMs=i)}function formatFixableErrorReply(e,t,n,r){let i=[t.message,``,`**How to fix**`,n,...t.id===void 0?[]:[``,`**Error id:**`,`\`${t.id}\``]].flatMap(e=>e.split(`
|
|
2
2
|
`)).map(e=>e.length>0?`> ${e}`:`> `).join(`
|
|
3
3
|
`);return[`${e}.`,``,i,...r?.followUp===void 0?[]:[``,r.followUp]].join(`
|
|
4
|
-
`)}function defaultSlackAuth(e,t){let n=e.author;return n?buildSlackAuthContext({channelId:t.slack.channelId,fullName:n.fullName,installationTeamId:e.installationTeamId,isBot:n.isBot,teamId:e.teamId,threadTs:t.slack.threadTs,userId:n.userId,userName:n.userName}):null}function defaultOnMessage(e,t){return{auth:defaultSlackAuth(t,e)}}async function defaultReceived(e,t){await t.thread.startTyping(`Thinking...`)}function firstNonEmptyLine(e){for(let t of e.split(/\r?\n/u)){let e=t.trim();if(e.length>0)return e}}async function showSlackSignIn(e,t){let{displayName:n}=e,r=slackUserIdForPrincipal(t.state,e.principalId)??null,i=e.url,a=t.state.pendingAuthMessageTs??{};if(e.responseId===void 0&&a[e.name]===void 0){let i=buildAuthRequiredPublicText({displayName:n,recipientUserId:r});try{let n=await t.thread.post(i);n.id&&(t.state.pendingAuthMessageTs={...a,[e.name]:n.id})}catch(t){log.error(`Slack auth public message delivery failed`,{name:e.name,error:t})}}let{instructions:o}=e;if(r&&(i||o)){let
|
|
4
|
+
`)}function defaultSlackAuth(e,t){let n=e.author;return n?buildSlackAuthContext({channelId:t.slack.channelId,fullName:n.fullName,installationTeamId:e.installationTeamId,isBot:n.isBot,teamId:e.teamId,threadTs:t.slack.threadTs,userId:n.userId,userName:n.userName}):null}function defaultOnMessage(e,t){return{auth:defaultSlackAuth(t,e)}}async function defaultReceived(e,t){await t.thread.startTyping(`Thinking...`)}function firstNonEmptyLine(e){for(let t of e.split(/\r?\n/u)){let e=t.trim();if(e.length>0)return e}}async function showSlackSignIn(e,t){let{displayName:n}=e,r=slackUserIdForPrincipal(t.state,e.principalId)??null,i=e.url,a=t.state.pendingAuthMessageTs??{};if(e.responseId===void 0&&a[e.name]===void 0){let i=buildAuthRequiredPublicText({displayName:n,recipientUserId:r});try{let n=await t.thread.post(i);n.id&&(t.state.pendingAuthMessageTs={...a,[e.name]:n.id})}catch(t){log.error(`Slack auth public message delivery failed`,{name:e.name,error:t})}}let{instructions:o}=e;if(r&&(i||o)){let a=slackSessionTargetOf(t.slack),s={cancel:e.responseId===void 0&&a.channelId&&(a.codeChannel||a.threadTs)&&e.turnId?{...a,turnId:e.turnId}:void 0,displayName:n,instructions:o,url:i,userCode:e.userCode};try{await t.thread.postEphemeral(r,{blocks:buildAuthEphemeralBlocks(s),text:buildAuthEphemeralText(s)})}catch(t){log.error(`Slack auth ephemeral delivery failed`,{name:e.name,error:t})}}}async function settleSlackSignIn(e,t,n){let{displayName:r}=e;t.outcome===`accepted`&&e.responseId===void 0&&await showStatus(n,`Connected to ${r}. Resuming...`,{force:!0});let i=n.state.pendingAuthMessageTs??{},a=i[e.name];if(a===void 0)return;let o=buildAuthCompletedText({displayName:r,outcome:t.outcome,reason:t.reason});try{await n.slack.request(`chat.update`,{channel:n.slack.channelId,ts:a,text:o})}catch(t){log.error(`Slack auth status edit failed`,{name:e.name,error:t})}let s={...i};delete s[e.name],n.state.pendingAuthMessageTs=s}function defaultInteractionEvents(e,t){return{async"interaction.opened"({data:n},r){let i=sessionTables(r.view),a=requestBatchOf(i,n);if(a!==void 0){await postRequestCards(a,r,t,e);return}let o=signInOf(i,n.interactionId);o!==void 0&&await showSlackSignIn(o,r.channel)},async"response.admitted"({data:e},n){let r=sessionTables(n.view).responses[e.responseId];r!==void 0&&await showAnswerChange(r.interactionId,n,t)},async"response.settled"({data:e},n){let r=sessionTables(n.view),i=r.responses[e.responseId];i!==void 0&&await showAnswerChange(i.interactionId,n,t);let a=refusedAnswerOf(r,e);if(a!==void 0)try{await notifyRefusedResponder(a,n.channel)}catch(e){logError(log,`failed to tell a refused responder why`,e,{interactionId:a.interactionId})}},async"interaction.settled"({data:e},n){let r=signInOf(sessionTables(n.view),e.interactionId);if(r!==void 0){await settleSlackSignIn(r,e,n.channel);return}await settleRequestCard(e.interactionId,n,t)}}}const defaultEvents={async"turn.paused"(e,{channel:t}){await hideStatus(t)},async"model.settled"(e,t){let n=e.scope?.turnId;if(n===void 0||waiting(sessionTables(t.view),n)?.on!==`work`)return;let r=workingTaskNames(sessionTables(t.view),n);r.length>0&&await showStatus(t.channel,waitingOnTasks(r))},async"model.started"(e,t){let{channel:n}=t,r=e.scope?.turnId;if(r===void 0)return;let i=n.state.pendingTaskResults;n.state.pendingTaskResults=null;let a=i?.turnId===r?reviewingResults(i.names):n.state.threadStatus?.text??`Thinking...`;await showStatus(n,a,{force:!0})},async"turn.started"(e,{channel:t}){t.state.pendingTaskResults=null,t.state.pendingToolCallMessage=null,streamByState.delete(t.state),await showStatus(t,`Thinking...`,{force:!0})},async"content.delta"(e,t){let{data:n}=e,{channel:r}=t,i=deltaStream(r.state,n,e.scope?.runId);if(i===void 0)return;let{kind:a,stream:o}=i;if(a===`reasoning`){o.reasoning===``&&(o.reasoningShownAtMs=null),o.reasoning+=n.delta,await showReasoning(r,o);return}if(a!==`text`)return;let s=o.replyChars;o.replyChars+=n.delta.length,s<280&&o.replyChars>=280&&await showStatus(r,`Writing a reply...`)},async"call.requested"({data:e},{channel:t}){let n=t.state.pendingToolCallMessage;t.state.pendingToolCallMessage=null;let{capability:r,owner:i}=e,a=!(`callId`in i)&&r.kind!==`control`;if(!n&&!a||!(`runId`in i))return;let o=stepStream(t.state,i.runId),s=callLabel(e),c=!n&&o.calls?{...o.calls,count:o.calls.count+1}:{count:+!!a,label:n??s,narrated:n!=null};o.calls=c,await showStatus(t,c.narrated?c.label:withMoreCalls(c.label,c.count))},async"call.progress"({data:e},{channel:t}){let n=e.label;n&&!heldWithin(t.state.threadStatus?.atMs,Date.now())&&await showStatus(t,n)},async"call.settled"(e,t){let{data:n}=e,{channel:r}=t,{taskId:i,turnId:a}=e.scope??{};if(i!==void 0&&a!==void 0&&n.outcome!==`interrupted`){let e=sessionTables(t.view).tasks[i],n=r.state.pendingTaskResults,o=n?.turnId===a?n.names:[],s=e?.kind===`agent`?e.name:null;r.state.pendingTaskResults={names:[...o,s],turnId:a}}n.label&&await showStatus(r,n.label)},async"content.completed"({data:e},t){let{channel:n}=t;if(e.kind===`reasoning`){let t=streamByState.get(n.state);if(t?.runId!==e.runId||typeof e.value!=`string`)return;t.reasoning=e.value,await showReasoning(n,t,{complete:!0}),t.reasoning=``;return}if(e.kind!==`text`)return;let r=typeof e.value==`string`?e.value:``;if(contentPhase(e.phase)===`narration`){n.state.pendingToolCallMessage=r?firstNonEmptyLine(r)??null:null;return}if(n.state.pendingToolCallMessage=null,!r){await clearStatus(n);return}n.state.threadStatus=null,await deliverCompletedSlackReply(n,r,{turnId:t.session.turn.id})},async"turn.settled"({data:e},{channel:t}){if(e.outcome!==`failed`){await clearStatus(t);return}t.state.codeChannel===!0&&await clearStatus(t);let n=e.error?.hint;if(e.error!==void 0&&n!==void 0){await t.thread.post(formatFixableErrorReply(`I hit an error while handling your request`,e.error,n));return}let r=e.error?.id,i=formatErrorHint(errorHintOf(e.error));await t.thread.post([`I hit an error while handling your request${i}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...r?[``,`_Error id: \`${r}\`_`]:[]].join(`
|
|
5
5
|
`))},async"session.ended"({data:e},{channel:t}){if(e.outcome!==`failed`)return;let n=e.error?.hint;if(e.error!==void 0&&n!==void 0){await t.thread.post(formatFixableErrorReply(`This session couldn't recover from an error`,e.error,n,{followUp:`Start a new thread to continue — I can't pick this one back up.`}));return}let r=e.error?.id,i=formatErrorHint(errorHintOf(e.error));await t.thread.post([`This session couldn't recover from an error${i}.`,``,`Start a new thread to continue — I can't pick this one back up.`,...r?[``,`_Error id: \`${r}\`_`]:[]].join(`
|
|
6
|
-
`))}};export{defaultEvents,
|
|
6
|
+
`))}};export{defaultEvents,defaultInteractionEvents,defaultOnMessage,defaultReceived,defaultSlackAuth,showSlackSignIn};
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
* UX grounds without changing the read path.
|
|
13
13
|
*/
|
|
14
14
|
import type { BlockKitBlock } from "#public/channels/slack/blocks.js";
|
|
15
|
+
import type { SlackInteractionCardParts } from "#public/channels/slack/renderers.js";
|
|
15
16
|
import { type Answer, type ValidatedAnswer } from "#shared/input.js";
|
|
16
17
|
import type { Interaction } from "#channel/interaction-prompts.js";
|
|
18
|
+
import type { SlackSessionTarget } from "#public/channels/slack/slackChannel.js";
|
|
17
19
|
/**
|
|
18
20
|
* Wire-format prefix every framework HITL widget mints onto its
|
|
19
21
|
* `action_id`. Exposed so end-user adapters that render their own
|
|
@@ -42,11 +44,8 @@ export declare const HITL_FREEFORM_MODAL_BLOCK_ID = "eve_freeform_block";
|
|
|
42
44
|
* `action_id` of the text input inside the freeform answer modal.
|
|
43
45
|
*/
|
|
44
46
|
export declare const HITL_FREEFORM_MODAL_ACTION_ID = "eve_freeform_text";
|
|
45
|
-
/** Slack
|
|
46
|
-
export
|
|
47
|
-
readonly channelId: string;
|
|
48
|
-
readonly threadTs: string;
|
|
49
|
-
}
|
|
47
|
+
/** Slack session a routed HITL widget answers back to. */
|
|
48
|
+
export type SlackHitlRoute = SlackSessionTarget;
|
|
50
49
|
/**
|
|
51
50
|
* Subset of one Slack interactivity action the HITL decoder reads.
|
|
52
51
|
* Mirrors the relevant fields of `SlackInteractionAction`.
|
|
@@ -114,19 +113,12 @@ export declare function decodeHitlActionId(actionId: string): DecodedHitlActionI
|
|
|
114
113
|
* Always emits at least the prompt section.
|
|
115
114
|
*/
|
|
116
115
|
export declare function renderInteractionBlocks(request: Interaction, route?: SlackHitlRoute): BlockKitBlock[];
|
|
117
|
-
export interface SlackInteractionPostPart {
|
|
118
|
-
readonly blocks: unknown[];
|
|
119
|
-
readonly text: string;
|
|
120
|
-
}
|
|
121
116
|
/**
|
|
122
117
|
* Splits approval details from their interactive controls. Slack includes the
|
|
123
118
|
* originating message in `block_actions`, so putting large tool input beside
|
|
124
119
|
* the buttons makes the callback body grow with model-authored input.
|
|
125
120
|
*/
|
|
126
|
-
export declare function renderInteractionPostParts(request: Interaction, route?: SlackHitlRoute):
|
|
127
|
-
readonly controls: SlackInteractionPostPart;
|
|
128
|
-
readonly details?: SlackInteractionPostPart;
|
|
129
|
-
};
|
|
121
|
+
export declare function renderInteractionPostParts(request: Interaction, route?: SlackHitlRoute): SlackInteractionCardParts;
|
|
130
122
|
/**
|
|
131
123
|
* Creates the fallback text for one HITL request. Slack clients use this
|
|
132
124
|
* outside the rich Block Kit surface, so include the same approval details
|
|
@@ -139,17 +131,15 @@ export declare function formatInteractionFallbackText(request: Interaction): str
|
|
|
139
131
|
* the modal to the `view_submission` that closes it so the route can
|
|
140
132
|
* deliver the answer back to the right session.
|
|
141
133
|
*/
|
|
142
|
-
export
|
|
134
|
+
export type HitlFreeformModalMetadata = SlackSessionTarget & {
|
|
143
135
|
readonly continuationToken: string;
|
|
144
|
-
readonly channelId: string;
|
|
145
136
|
/** Workspace whose app installation supplies credentials for modal follow-up API calls. */
|
|
146
137
|
readonly installationTeamId?: string;
|
|
147
|
-
readonly threadTs: string;
|
|
148
138
|
/** Slack conversation containing the interactive card. */
|
|
149
139
|
readonly messageChannelId?: string;
|
|
150
140
|
readonly messageTs: string;
|
|
151
141
|
readonly requestId: string;
|
|
152
|
-
}
|
|
142
|
+
};
|
|
153
143
|
/**
|
|
154
144
|
* Builds the `views.open` payload for the freeform-answer modal. The
|
|
155
145
|
* triggering `prompt` is preserved as a header section so the user can
|