experimental-ash 0.51.0 → 0.53.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/docs/public/advanced/instrumentation.md +87 -119
  3. package/dist/docs/public/advanced/session-context.md +4 -2
  4. package/dist/docs/public/advanced/typescript-api.md +4 -2
  5. package/dist/docs/public/channels/discord.mdx +2 -2
  6. package/dist/docs/public/channels/index.md +4 -4
  7. package/dist/docs/public/channels/teams.mdx +1 -1
  8. package/dist/docs/public/channels/telegram.mdx +2 -2
  9. package/dist/docs/public/meta.json +1 -0
  10. package/dist/docs/public/onboarding.md +119 -0
  11. package/dist/docs/public/schedules.mdx +4 -4
  12. package/dist/docs/public/subagents.mdx +1 -1
  13. package/dist/src/channel/compiled-channel.d.ts +2 -2
  14. package/dist/src/channel/cross-channel-receive.d.ts +6 -6
  15. package/dist/src/channel/cross-channel-receive.js +1 -1
  16. package/dist/src/channel/receive-target.d.ts +17 -0
  17. package/dist/src/channel/types.d.ts +4 -0
  18. package/dist/src/cli/commands/channels.d.ts +2 -0
  19. package/dist/src/cli/commands/channels.js +1 -1
  20. package/dist/src/cli/commands/info.d.ts +46 -1
  21. package/dist/src/cli/commands/info.js +2 -2
  22. package/dist/src/cli/run.d.ts +3 -1
  23. package/dist/src/cli/run.js +2 -2
  24. package/dist/src/execution/ash-workflow-attributes.d.ts +18 -0
  25. package/dist/src/execution/ash-workflow-attributes.js +1 -1
  26. package/dist/src/execution/create-session-step.js +1 -1
  27. package/dist/src/execution/subagent-tool.js +1 -1
  28. package/dist/src/harness/{instrumentation-metadata.d.ts → instrumentation-runtime-context.d.ts} +2 -2
  29. package/dist/src/harness/instrumentation-runtime-context.js +1 -0
  30. package/dist/src/harness/tool-loop.js +1 -1
  31. package/dist/src/internal/application/package.js +1 -1
  32. package/dist/src/internal/instrumentation.d.ts +8 -7
  33. package/dist/src/internal/instrumentation.js +1 -1
  34. package/dist/src/packages/ash-scaffold/src/channels.js +2 -2
  35. package/dist/src/packages/ash-scaffold/src/human-action.js +1 -0
  36. package/dist/src/packages/ash-scaffold/src/index.js +1 -1
  37. package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +1 -1
  38. package/dist/src/packages/ash-scaffold/src/steps/setup-slackbot.js +1 -1
  39. package/dist/src/public/channels/discord/discordChannel.d.ts +3 -3
  40. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  41. package/dist/src/public/channels/discord/index.d.ts +1 -1
  42. package/dist/src/public/channels/slack/index.d.ts +1 -1
  43. package/dist/src/public/channels/slack/slackChannel.d.ts +4 -3
  44. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  45. package/dist/src/public/channels/teams/index.d.ts +1 -1
  46. package/dist/src/public/channels/teams/teamsChannel.d.ts +3 -3
  47. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  48. package/dist/src/public/channels/telegram/index.d.ts +1 -1
  49. package/dist/src/public/channels/telegram/telegramChannel.d.ts +3 -3
  50. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  51. package/dist/src/public/channels/twilio/index.d.ts +1 -1
  52. package/dist/src/public/channels/twilio/twilioChannel.d.ts +3 -3
  53. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  54. package/dist/src/public/definitions/defineChannel.d.ts +8 -8
  55. package/dist/src/public/definitions/schedule.d.ts +2 -2
  56. package/dist/src/public/instrumentation/index.d.ts +21 -11
  57. package/dist/src/public/schedules/index.d.ts +1 -1
  58. package/package.json +1 -1
  59. package/dist/src/channel/receive-args.d.ts +0 -17
  60. package/dist/src/harness/instrumentation-metadata.js +0 -1
  61. /package/dist/src/channel/{receive-args.js → receive-target.js} +0 -0
@@ -1,2 +1,2 @@
1
- import{deriveSlackConnectorSlug,ensureChannel}from"../channels.js";import{createPromptCommandOutput}from"../cli/command-output.js";import{runVercel}from"../primitives/run-vercel.js";import{detectDeployment}from"../primitives/detect-deployment.js";import{deployToVercel}from"./deploy-to-vercel.js";import"../cli/channel-setup-prompter.js";import{runPnpmInstall}from"../primitives/run-pnpm.js";import{reconcileSlackUid,setupSlackbot}from"./setup-slackbot.js";const CHANNEL_OPTIONS=[{value:`web`,label:`Web Chat`},{value:`slack`,label:`Slack`,hint:`creates slackbot and deploys to Vercel`}];function selectableChannels(e){return CHANNEL_OPTIONS.map(t=>{let n=e?.[t.value];return n===void 0?t:{...t,disabled:!0,disabledReason:n}})}function createAddToAgentState(e={state:`unlinked`}){return{channels:[],webScaffolded:!1,slackScaffolded:!1,deploymentDependenciesInstalled:!1,projectLinked:e.state!==`unlinked`,vercelProjectId:e.projectId,deployed:e.state===`deployed`,deploymentPending:!1,productionUrl:e.productionUrl,slackbotCreated:!1,slackbotAttached:!1,slackConnectorUid:void 0,slackWorkspaceUrl:void 0,slackWorkspaceName:void 0}}function recordScaffoldedChannel(e,t){e.channels.includes(t)||(e.channels.push(t),e.deploymentPending=!0)}async function promptCreateSlackbot(e){return await e.select({message:`Do you want to create your slackbot?`,options:[{value:`yes`,label:`Yes`,hint:`connects Slack, then deploys`},{value:`no`,label:`No`}]})===`yes`}async function installDeploymentDependencies(e,t,r){if(!r.deploymentDependenciesInstalled){if(e.log.message(`Installing project dependencies before deployment (pnpm install)...`),!await runPnpmInstall(t,{onOutput:createPromptCommandOutput(e.log)}))throw Error(`Dependency installation failed. Deployment did not start.`);r.deploymentDependenciesInstalled=!0}}async function linkProjectForSlackbot(e){if(e.state.projectLinked)return;let t;if(e.linkProjectForSlackbot===void 0?(e.prompter.log.message(`Linking this directory to a Vercel project...`),t=await runVercel([`link`],{cwd:e.projectPath})):t=await e.linkProjectForSlackbot(),!t)throw Error(`Vercel project linking failed. Slackbot creation did not start.`);e.state.projectLinked=!0;let n=await detectDeployment(e.projectPath);e.state.vercelProjectId=n.projectId??e.state.vercelProjectId,e.state.productionUrl=n.productionUrl??e.state.productionUrl,e.state.deployed=e.state.deployed||n.state===`deployed`}async function scaffoldSlackChannel(e,n){let{prompter:r,projectPath:i,state:a}=e;if(!a.slackScaffolded){r.log.message(`Scaffolding Slack channel files...`);let o=await ensureChannel({projectRoot:i,kind:`slack`,slackConnectorSlug:n,force:e.force});o.action===`created`||o.action===`overwritten`?r.log.success(`Scaffolded channel: slack`):r.log.info(`Channel "slack" already exists. Skipping file creation.`),a.slackScaffolded=!0}recordScaffoldedChannel(a,`slack`)}async function linkProjectForDeployment(e){let{prompter:t,projectPath:i,state:a}=e;if(a.projectLinked)return;let o=e.vercelProjectId??a.vercelProjectId,s=createPromptCommandOutput(t.log);if(o===void 0){if(t.log.message(`Linking this directory to a Vercel project before deployment...`),!await runVercel([`link`],{cwd:i,onOutput:s}))throw Error(`Vercel project linking failed. Deployment did not start.`)}else{if(t.log.message(`Linking this directory to Vercel project "${o}" before deployment...`),!await runVercel([`link`,`--project`,o,`--yes`],{cwd:i,onOutput:s}))throw Error(`Vercel project linking failed. Deployment did not start.`);a.vercelProjectId=o}a.projectLinked=!0}async function deployProject(e){let{prompter:t,projectPath:n,state:r}=e;if(!r.deploymentPending)return;await linkProjectForDeployment(e),await installDeploymentDependencies(t,n,r);let i=await deployToVercel(t,n,{presetDeploy:!0});if(r.deployed=i.deployed,r.productionUrl=i.productionUrl??r.productionUrl,!r.deployed)throw Error(`Deployment failed after channel setup.`);r.deploymentPending=!1}async function reconcilePreparedSlackChannel(e,t,n,r){let i=n.slackConnectorUid;if(i===void 0)throw Error(`Slack connector UID was not resolved. Slack deployment did not start.`);if(!await reconcileSlackUid(e,t,{kind:`attached`,created:!0,attached:!0,connectorUid:i},r))throw Error(`Slack connector UID update is required before deployment.`)}function recordAttachedSlackbot(e,t){e.slackbotCreated=!0,e.slackbotAttached=!0,e.slackConnectorUid=t.connectorUid,e.slackWorkspaceUrl=t.workspaceUrl,e.slackWorkspaceName=t.workspaceName}async function runAddToAgentOnce(n,r){let{prompter:i,projectPath:a,projectName:o,state:s}=n;if(r.includes(`web`))if(s.webScaffolded)recordScaffoldedChannel(s,`web`);else{i.log.message(`Scaffolding Web Chat channel files...`);let e=await ensureChannel({projectRoot:a,kind:`web`,force:n.force,webPackageVersions:n.webPackageVersions});e.action===`created`||e.action===`overwritten`?(i.log.success(`Scaffolded channel: web`),s.webScaffolded=!0,recordScaffoldedChannel(s,`web`)):i.log.info(`Next.js project detected. Skipping Web Chat scaffolding.`)}if(r.includes(`slack`)){let t=await deriveSlackConnectorSlug(a,o);if(s.slackbotCreated){if(!s.slackbotAttached)throw Error(`Slackbot provisioning did not attach this project. Slack channel was not added.`);s.deploymentPending&&(await scaffoldSlackChannel(n,t),await reconcilePreparedSlackChannel(i,a,s,`slack/${t}`))}else if(n.presetCreateSlackbot??await promptCreateSlackbot(i)){await linkProjectForSlackbot(n);let e=await setupSlackbot(i,a,{presetCreate:!0,slackbotName:t});if(!e.created)throw Error(`Slackbot creation failed.`);if(e.kind!==`attached`)throw Error(`Slackbot provisioning did not attach this project. Slack channel was not added.`);recordAttachedSlackbot(s,e),await scaffoldSlackChannel(n,t),await reconcilePreparedSlackChannel(i,a,s,`slack/${t}`)}else i.log.info(`Slack channel was not added because Slackbot setup was skipped.`)}}async function runAddToAgent(e){for(;;){let t=e.presetChannels??await e.prompter.multiselect({message:`Add to agent`,options:selectableChannels(e.disabledChannelReasons),required:!1});await e.validateSelectedChannels?.(t);try{await runAddToAgentOnce(e,t);return}catch(t){if(t instanceof Error&&(t.name===`WizardCancelledError`||t.name===`ChannelAddCancelledError`))throw t;let n=t instanceof Error?t.message:String(t),r=n.split(`
1
+ import{deriveSlackConnectorSlug,ensureChannel}from"../channels.js";import{HumanActionRequiredError}from"../human-action.js";import{createPromptCommandOutput}from"../cli/command-output.js";import{runVercel}from"../primitives/run-vercel.js";import{detectDeployment}from"../primitives/detect-deployment.js";import{deployToVercel}from"./deploy-to-vercel.js";import"../cli/channel-setup-prompter.js";import{runPnpmInstall}from"../primitives/run-pnpm.js";import{reconcileSlackUid,setupSlackbot}from"./setup-slackbot.js";const CHANNEL_OPTIONS=[{value:`web`,label:`Web Chat`},{value:`slack`,label:`Slack`,hint:`creates slackbot and deploys to Vercel`}];function selectableChannels(e){return CHANNEL_OPTIONS.map(t=>{let n=e?.[t.value];return n===void 0?t:{...t,disabled:!0,disabledReason:n}})}function createAddToAgentState(e={state:`unlinked`}){return{channels:[],webScaffolded:!1,slackScaffolded:!1,deploymentDependenciesInstalled:!1,projectLinked:e.state!==`unlinked`,vercelProjectId:e.projectId,deployed:e.state===`deployed`,deploymentPending:!1,productionUrl:e.productionUrl,slackbotCreated:!1,slackbotAttached:!1,slackConnectorUid:void 0,slackWorkspaceUrl:void 0,slackWorkspaceName:void 0}}function recordScaffoldedChannel(e,t){e.channels.includes(t)||(e.channels.push(t),e.deploymentPending=!0)}async function promptCreateSlackbot(e){return await e.select({message:`Do you want to create your slackbot?`,options:[{value:`yes`,label:`Yes`,hint:`connects Slack, then deploys`},{value:`no`,label:`No`}]})===`yes`}async function installDeploymentDependencies(e,t,n){if(!n.deploymentDependenciesInstalled){if(e.log.message(`Installing project dependencies before deployment (pnpm install)...`),!await runPnpmInstall(t,{onOutput:createPromptCommandOutput(e.log)}))throw Error(`Dependency installation failed. Deployment did not start.`);n.deploymentDependenciesInstalled=!0}}async function linkProjectForSlackbot(e){if(e.state.projectLinked)return;let t;if(e.linkProjectForSlackbot!==void 0)t=await e.linkProjectForSlackbot();else if(e.headless)throw new HumanActionRequiredError({kind:`vercel-link`,command:`vercel link`,reason:`Slack needs this directory linked to a Vercel project.`});else e.prompter.log.message(`Linking this directory to a Vercel project...`),t=await runVercel([`link`],{cwd:e.projectPath});if(!t)throw Error(`Vercel project linking failed. Slackbot creation did not start.`);e.state.projectLinked=!0;let r=await detectDeployment(e.projectPath);e.state.vercelProjectId=r.projectId??e.state.vercelProjectId,e.state.productionUrl=r.productionUrl??e.state.productionUrl,e.state.deployed=e.state.deployed||r.state===`deployed`}async function scaffoldSlackChannel(e,n){let{prompter:r,projectPath:i,state:a}=e;if(!a.slackScaffolded){r.log.message(`Scaffolding Slack channel files...`);let o=await ensureChannel({projectRoot:i,kind:`slack`,slackConnectorSlug:n,force:e.force});o.action===`created`||o.action===`overwritten`?r.log.success(`Scaffolded channel: slack`):r.log.info(`Channel "slack" already exists. Skipping file creation.`),a.slackScaffolded=!0}recordScaffoldedChannel(a,`slack`)}async function linkProjectForDeployment(e){let{prompter:t,projectPath:a,state:o}=e;if(o.projectLinked)return;let s=e.vercelProjectId??o.vercelProjectId,c=createPromptCommandOutput(t.log);if(s===void 0){if(e.headless)throw new HumanActionRequiredError({kind:`vercel-link`,command:`vercel link`,reason:`Deployment needs this directory linked to a Vercel project.`});if(t.log.message(`Linking this directory to a Vercel project before deployment...`),!await runVercel([`link`],{cwd:a,onOutput:c}))throw Error(`Vercel project linking failed. Deployment did not start.`)}else{if(t.log.message(`Linking this directory to Vercel project "${s}" before deployment...`),!await runVercel([`link`,`--project`,s,`--yes`],{cwd:a,onOutput:c}))throw Error(`Vercel project linking failed. Deployment did not start.`);o.vercelProjectId=s}o.projectLinked=!0}async function deployProject(e){let{prompter:t,projectPath:n,state:r}=e;if(!r.deploymentPending)return;await linkProjectForDeployment(e),await installDeploymentDependencies(t,n,r);let i=await deployToVercel(t,n,{presetDeploy:!0});if(r.deployed=i.deployed,r.productionUrl=i.productionUrl??r.productionUrl,!r.deployed)throw Error(`Deployment failed after channel setup.`);r.deploymentPending=!1}async function reconcilePreparedSlackChannel(e,t,n,r){let i=n.slackConnectorUid;if(i===void 0)throw Error(`Slack connector UID was not resolved. Slack deployment did not start.`);if(!await reconcileSlackUid(e,t,{kind:`attached`,created:!0,attached:!0,connectorUid:i},r))throw Error(`Slack connector UID update is required before deployment.`)}function recordAttachedSlackbot(e,t){e.slackbotCreated=!0,e.slackbotAttached=!0,e.slackConnectorUid=t.connectorUid,e.slackWorkspaceUrl=t.workspaceUrl,e.slackWorkspaceName=t.workspaceName}async function runAddToAgentOnce(n,r){let{prompter:i,projectPath:a,projectName:o,state:s}=n;if(n.headless&&r.includes(`slack`))throw Error(`Slack setup is interactive. Run the guided create flow with -i, or add Slack from the onboarding steps after headless scaffolding.`);if(r.includes(`web`))if(s.webScaffolded)recordScaffoldedChannel(s,`web`);else{i.log.message(`Scaffolding Web Chat channel files...`);let e=await ensureChannel({projectRoot:a,kind:`web`,force:n.force,webPackageVersions:n.webPackageVersions});e.action===`created`||e.action===`overwritten`?(i.log.success(`Scaffolded channel: web`),s.webScaffolded=!0,recordScaffoldedChannel(s,`web`)):i.log.info(`Next.js project detected. Skipping Web Chat scaffolding.`)}if(r.includes(`slack`)){let t=await deriveSlackConnectorSlug(a,o);if(s.slackbotCreated){if(!s.slackbotAttached)throw Error(`Slackbot provisioning did not attach this project. Slack channel was not added.`);s.deploymentPending&&(await scaffoldSlackChannel(n,t),await reconcilePreparedSlackChannel(i,a,s,`slack/${t}`))}else if(n.presetCreateSlackbot??await promptCreateSlackbot(i)){await linkProjectForSlackbot(n);let e=await setupSlackbot(i,a,{presetCreate:!0,slackbotName:t});if(!e.created)throw Error(`Slackbot creation failed.`);if(e.kind!==`attached`)throw Error(`Slackbot provisioning did not attach this project. Slack channel was not added.`);recordAttachedSlackbot(s,e),await scaffoldSlackChannel(n,t),await reconcilePreparedSlackChannel(i,a,s,`slack/${t}`)}else i.log.info(`Slack channel was not added because Slackbot setup was skipped.`)}}async function runAddToAgent(e){for(;;){let t=e.presetChannels??await e.prompter.multiselect({message:`Add to agent`,options:selectableChannels(e.disabledChannelReasons),required:!1});await e.validateSelectedChannels?.(t);try{await runAddToAgentOnce(e,t);return}catch(t){if(t instanceof Error&&(t.name===`WizardCancelledError`||t.name===`ChannelAddCancelledError`))throw t;let n=t instanceof Error?t.message:String(t),r=n.split(`
2
2
  `)[0]?.trim()??n;if(e.prompter.log.error(r),e.presetChannels!==void 0)throw t}}}export{createAddToAgentState,deployProject,runAddToAgent};
@@ -1 +1 @@
1
- import{SLACK_CHANNEL_DEFAULT_ROUTE}from"../channels.js";import{createPromptCommandOutput}from"../cli/command-output.js";import{captureVercel,runVercel}from"../primitives/run-vercel.js";import{updateSlackChannelConnectorUid}from"../primitives/update-slack-channel.js";import{join}from"node:path";import{readFile}from"node:fs/promises";async function runVercelConnectCreateSlack(e,t){let n=[`connect`,`create`,`slack`,`--triggers`];return t&&n.push(`--name`,t),runVercel(n,{cwd:e})}async function readProjectId(e){try{let t=await readFile(join(e,`.vercel`,`project.json`),`utf8`),n=JSON.parse(t);return typeof n.projectId==`string`?n.projectId:void 0}catch{return}}function pickSlackConnector(e,t,n){if(typeof e!=`object`||!e)return;let r=e.clients;if(!Array.isArray(r))return;let i;for(let e of r){if(e.type!==`slack`||typeof e.uid!=`string`||typeof e.id!=`string`)continue;let r={uid:e.uid,id:e.id};if(n!==void 0&&r.uid===n)return r;let a=typeof e.createdAt==`number`?e.createdAt:0;t!==void 0&&Array.isArray(e.projects)&&e.projects.some(e=>typeof e==`object`&&!!e&&e.id===t)&&(!i||a>i.createdAt)&&(i={ref:r,createdAt:a})}return i?.ref}function parseInstallation(e){if(typeof e!=`object`||!e)return;let t=e.installations;if(!Array.isArray(t)||t.length===0)return;let n=t[0];if(typeof n.tenantUrl==`string`)return{workspaceUrl:n.tenantUrl,workspaceName:typeof n.tenantName==`string`?n.tenantName:void 0}}async function findSlackConnector(e,t,r,i){let a=await captureVercel([`connect`,`list`,`-F`,`json`,`--all-projects`],{cwd:e,onOutput:i});if(a)try{return pickSlackConnector(JSON.parse(a),t,r)}catch{return}}async function fetchInstallationInfo(e,t,r){let i=await captureVercel([`api`,`/v1/connex/clients/${t}/installations`],{cwd:e,onOutput:r});if(i)try{return parseInstallation(JSON.parse(i))}catch{return}}async function setupSlackbot(n,i,a={}){let o=a.presetCreate;if(o===void 0&&(o=await n.select({message:`Do you want to create your slackbot?`,options:[{value:`yes`,label:`Yes`},{value:`no`,label:`No`}]})===`yes`),!o)return n.log.info("Skipping slackbot creation. Run `vercel connect create slack` later when ready."),{kind:`skipped`,created:!1,attached:!1};let s=createPromptCommandOutput(n.log);if(n.log.message(`Creating a Slackbot through Vercel Connect...`),!await runVercelConnectCreateSlack(i,a.slackbotName))return{kind:`create-failed`,created:!1,attached:!1};n.log.success(`Slackbot created via Vercel Connect.`),n.log.message(`Locating the Slack connector...`);let c=await findSlackConnector(i,await readProjectId(i),a.slackbotName?`slack/${a.slackbotName}`:void 0,s);if(!c)return n.log.warning("Could not locate the new Slack connector. Run `vercel connect list --all-projects` to find its UID."),{kind:`connector-unresolved`,created:!0,attached:!1};if(n.log.info(`Slack connector: ${c.uid}`),n.log.message(`Configuring Slack event delivery for this agent...`),await runVercel([`connect`,`detach`,c.uid,`--yes`],{cwd:i,onOutput:s}),!await runVercel([`connect`,`attach`,c.uid,`--triggers`,`--trigger-path`,SLACK_CHANNEL_DEFAULT_ROUTE,`--yes`],{cwd:i,onOutput:s}))return n.log.warning(`Could not register this project as a trigger destination. Run \`vercel connect attach ${c.uid} --triggers --trigger-path ${SLACK_CHANNEL_DEFAULT_ROUTE} --yes\` to enable event delivery.`),{kind:`attach-failed`,created:!0,attached:!1,connectorUid:c.uid};n.log.message(`Reading Slack workspace details...`);let l=await fetchInstallationInfo(i,c.id,s);return l?{kind:`attached`,created:!0,attached:!0,connectorUid:c.uid,workspaceUrl:l.workspaceUrl,workspaceName:l.workspaceName}:{kind:`attached`,created:!0,attached:!0,connectorUid:c.uid}}async function reconcileSlackUid(e,t,n,r){if(n.kind!==`attached`||n.connectorUid===r)return!0;e.log.info(`Connect assigned UID ${n.connectorUid} (expected ${r}). Updating agent/channels/slack.ts before deployment...`);let{patched:o}=await updateSlackChannelConnectorUid(join(t,`agent/channels/slack.ts`),n.connectorUid);return o?!0:(e.log.warning(`Could not patch agent/channels/slack.ts automatically. Update \`connectSlackCredentials("...")\` to \`"${n.connectorUid}"\` and run \`vercel deploy --prod\`.`),!1)}export{reconcileSlackUid,setupSlackbot};
1
+ import{SLACK_CHANNEL_DEFAULT_ROUTE}from"../channels.js";import{createPromptCommandOutput}from"../cli/command-output.js";import{captureVercel,runVercel}from"../primitives/run-vercel.js";import{updateSlackChannelConnectorUid}from"../primitives/update-slack-channel.js";import{join}from"node:path";import{readFile}from"node:fs/promises";async function runVercelConnectCreateSlack(e,t){let n=[`connect`,`create`,`slack`,`--triggers`];return t&&n.push(`--name`,t),runVercel(n,{cwd:e})}async function readProjectId(e){try{let t=await readFile(join(e,`.vercel`,`project.json`),`utf8`),n=JSON.parse(t);return typeof n.projectId==`string`?n.projectId:void 0}catch{return}}function pickSlackConnector(e,t,n){if(typeof e!=`object`||!e)return;let r=e.clients;if(!Array.isArray(r))return;let i;for(let e of r){if(e.type!==`slack`||typeof e.uid!=`string`||typeof e.id!=`string`)continue;let r={uid:e.uid,id:e.id};if(n!==void 0&&r.uid===n)return r;let a=typeof e.createdAt==`number`?e.createdAt:0;t!==void 0&&Array.isArray(e.projects)&&e.projects.some(e=>typeof e==`object`&&!!e&&e.id===t)&&(!i||a>i.createdAt)&&(i={ref:r,createdAt:a})}return i?.ref}function parseInstallation(e){if(typeof e!=`object`||!e)return;let t=e.installations;if(!Array.isArray(t)||t.length===0)return;let n=t[0];if(typeof n.tenantUrl==`string`)return{workspaceUrl:n.tenantUrl,workspaceName:typeof n.tenantName==`string`?n.tenantName:void 0}}async function findSlackConnector(e,t,r,i){let a=await captureVercel([`connect`,`list`,`-F`,`json`,`--all-projects`],{cwd:e,onOutput:i});if(a)try{return pickSlackConnector(JSON.parse(a),t,r)}catch{return}}async function fetchInstallationInfo(e,t,r){let i=await captureVercel([`api`,`/v1/connex/clients/${t}/installations`],{cwd:e,onOutput:r});if(i)try{return parseInstallation(JSON.parse(i))}catch{return}}async function setupSlackbot(n,i,a={}){let o=a.presetCreate;if(o===void 0&&(o=await n.select({message:`Do you want to create your slackbot?`,options:[{value:`yes`,label:`Yes`},{value:`no`,label:`No`}]})===`yes`),!o)return n.log.info("Skipping slackbot creation. Run `vercel connect create slack` later when ready."),{kind:`skipped`,created:!1,attached:!1};let s=createPromptCommandOutput(n.log),c=await readProjectId(i),l=a.slackbotName?`slack/${a.slackbotName}`:void 0;if(n.log.message(`Creating a Slackbot through Vercel Connect...`),!await runVercelConnectCreateSlack(i,a.slackbotName))return{kind:`create-failed`,created:!1,attached:!1};n.log.success(`Slackbot created via Vercel Connect.`),n.log.message(`Locating the Slack connector...`);let u=await findSlackConnector(i,c,l,s);if(!u)return n.log.warning("Could not locate the Slack connector. Run `vercel connect list --all-projects` to find its UID."),{kind:`connector-unresolved`,created:!0,attached:!1};if(n.log.info(`Slack connector: ${u.uid}`),n.log.message(`Configuring Slack event delivery for this agent...`),await runVercel([`connect`,`detach`,u.uid,`--yes`],{cwd:i,onOutput:s}),!await runVercel([`connect`,`attach`,u.uid,`--triggers`,`--trigger-path`,SLACK_CHANNEL_DEFAULT_ROUTE,`--yes`],{cwd:i,onOutput:s}))return n.log.warning(`Could not register this project as a trigger destination. Run \`vercel connect attach ${u.uid} --triggers --trigger-path ${SLACK_CHANNEL_DEFAULT_ROUTE} --yes\` to enable event delivery.`),{kind:`attach-failed`,created:!0,attached:!1,connectorUid:u.uid};n.log.message(`Reading Slack workspace details...`);let d=await fetchInstallationInfo(i,u.id,s);return d?{kind:`attached`,created:!0,attached:!0,connectorUid:u.uid,workspaceUrl:d.workspaceUrl,workspaceName:d.workspaceName}:{kind:`attached`,created:!0,attached:!0,connectorUid:u.uid}}async function reconcileSlackUid(e,t,n,r){if(n.kind!==`attached`||n.connectorUid===r)return!0;e.log.info(`Connect assigned UID ${n.connectorUid} (expected ${r}). Updating agent/channels/slack.ts before deployment...`);let{patched:o}=await updateSlackChannelConnectorUid(join(t,`agent/channels/slack.ts`),n.connectorUid);return o?!0:(e.log.warning(`Could not patch agent/channels/slack.ts automatically. Update \`connectSlackCredentials("...")\` to \`"${n.connectorUid}"\` and run \`vercel deploy --prod\`.`),!1)}export{reconcileSlackUid,setupSlackbot};
@@ -49,8 +49,8 @@ export interface DiscordChannelCredentials extends DiscordCredentials {
49
49
  */
50
50
  readonly webhookVerifier?: DiscordWebhookVerifier;
51
51
  }
52
- /** Arguments accepted by `receive(discord, args)` for proactive sessions. */
53
- export interface DiscordReceiveArgs {
52
+ /** Target accepted by `receive(discord, { target })` for proactive sessions. */
53
+ export interface DiscordReceiveTarget {
54
54
  readonly channelId: string;
55
55
  readonly conversationId?: string;
56
56
  readonly initialMessage?: string | DiscordMessageBody;
@@ -126,7 +126,7 @@ export interface DiscordRequestOptions {
126
126
  readonly method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
127
127
  }
128
128
  /** Concrete return type of {@link discordChannel}. */
129
- export interface DiscordChannel extends Channel<DiscordChannelState, DiscordReceiveArgs> {
129
+ export interface DiscordChannel extends Channel<DiscordChannelState, DiscordReceiveTarget> {
130
130
  }
131
131
  /** Discord channel factory for HTTP Interactions and proactive channel messages. */
132
132
  export declare function discordChannel(config?: DiscordChannelConfig): DiscordChannel;
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import"#public/channels/discord/verify.js";import{callDiscordApi,createDiscordFollowupMessage,discordContinuationToken,editDiscordOriginalResponse,sendDiscordChannelMessage,splitDiscordMessageContent,triggerDiscordTypingIndicator}from"#public/channels/discord/api.js";import{buildFreeformModalResponse,deriveComponentInputResponses,deriveModalInputResponses,isDiscordFreeformComponent}from"#public/channels/discord/hitl.js";import{defaultEvents,defaultOnCommand}from"#public/channels/discord/defaults.js";import{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{verifyDiscordInbound}from"#public/channels/discord/verifyInbound.js";const log=createLogger(`discord.channel`);function discordChannel(e={}){let t=e.onCommand??defaultOnCommand,a={...defaultEvents,...e.events};return defineChannel({kindHint:`discord`,state:initialDiscordState(),context(t,n){return rebuildDiscordContext(t,n,e)},routes:[POST(e.route??`/ash/v1/discord`,async(r,{send:i,waitUntil:a})=>{let o=await verifyDiscordInbound(r,e.credentials);if(o===null)return new Response(`unauthorized`,{status:401});let s;try{s=parseJsonObject(JSON.parse(o))}catch(e){return log.warn(`inbound Discord body is not valid JSON`,{error:e}),discordJson({content:`invalid request`,ephemeral:!0})}if(s.type===DISCORD_INTERACTION_TYPE.PING)return discordJson({type:DISCORD_INTERACTION_RESPONSE_TYPE.PONG});let c=parseDiscordInteraction(s);return c===null?discordJson({content:`Unsupported Discord interaction.`,ephemeral:!0}):handleInteraction({config:e,interaction:c,onCommand:t,send:i,waitUntil:a})})],async receive(t,{send:n}){let r=t.args,i=readString(r.channelId);if(!i)throw Error(`discordChannel().receive requires args.channelId.`);let a=readString(r.conversationId),o=r.initialMessage;if(o!==void 0&&a!==void 0)throw Error("discordChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=a??``,l=a!==void 0;if(o!==void 0){let t=await buildDiscordHandle({config:e,state:{...initialDiscordState(),channelId:i}}).sendChannelMessage(o);c=t.id,l=t.id.length>0}return n(t.message,{auth:t.auth,continuationToken:discordContinuationToken(i,c),state:{applicationId:null,channelId:i,conversationId:c||null,guildId:null,hasMessageAnchor:l,initialResponseSent:!0,interactionToken:null}})},events:a})}function rebuildDiscordContext(e,t,n){return{discord:buildDiscordHandle({config:n,session:t,state:e}),state:e}}function buildDiscordHandle(e){let n=e.config.api,r=e.state,i=mergeCredentials(e.config.credentials,r);function anchor(t){!t.id||r.hasMessageAnchor||(r.conversationId=t.id,r.hasMessageAnchor=!0,r.channelId&&e.session?.setContinuationToken(discordContinuationToken(r.channelId,t.id)))}async function sendViaChannel(e){let t=r.channelId??``;if(!t)throw Error(`discordChannel: missing channel id for outbound message.`);let a=await sendDiscordChannelMessage({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,channelId:t});return anchor(a),a}async function editOriginal(e){let t=r.interactionToken??``;if(!t)throw Error(`discordChannel: missing interaction token for original response edit.`);let a=await editDiscordOriginalResponse({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,interactionToken:t});return r.initialResponseSent=!0,anchor(a),a}async function followup(e){let t=r.interactionToken??``;if(!t)throw Error(`discordChannel: missing interaction token for followup message.`);let a=await createDiscordFollowupMessage({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,interactionToken:t});return anchor(a),a}async function startTyping(){let e=r.channelId??``;if(e)try{await triggerDiscordTypingIndicator({apiBaseUrl:n?.apiBaseUrl,credentials:i,fetch:n?.fetch,channelId:e})}catch(n){logError(log,`Discord typing indicator failed — swallowed`,n,{channelId:e})}}return{applicationId:r.applicationId??void 0,channelId:r.channelId??``,conversationId:r.conversationId??``,guildId:r.guildId??void 0,interactionToken:r.interactionToken??void 0,request(e,t,r){return callDiscordApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:r?.botAuth===!0?i.botToken:void 0,fetch:n?.fetch,method:r?.method,path:e})},async post(e){let t=expandPostBodies(normalizePostInput(e)),n;for(let e of t){let t=await postOne({body:e,editOriginal,followup,sendViaChannel,state:r});n===void 0&&(n=t)}return n??{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){return e.interaction.type===DISCORD_INTERACTION_TYPE.APPLICATION_COMMAND?handleCommandInteraction({config:e.config,interaction:e.interaction,onCommand:e.onCommand,send:e.send,waitUntil:e.waitUntil}):e.interaction.type===DISCORD_INTERACTION_TYPE.MESSAGE_COMPONENT?handleComponentInteraction({interaction:e.interaction,send:e.send,waitUntil:e.waitUntil}):handleModalSubmitInteraction({interaction:e.interaction,send:e.send,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,send:e.send,state:t})),discordDeferredJson(r.ephemeral===!0))}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=deriveComponentInputResponses(e.interaction);return t.length>0&&e.waitUntil(dispatchInputResponses({conversationId:e.interaction.messageId,inputResponses:t,interaction:e.interaction,send:e.send})),discordJsonBody({type:DISCORD_INTERACTION_RESPONSE_TYPE.DEFERRED_UPDATE_MESSAGE})}function handleModalSubmitInteraction(e){let t=deriveModalInputResponses(e.interaction);return t.length>0&&e.waitUntil(dispatchInputResponses({conversationId:e.interaction.messageId??e.interaction.id,inputResponses:t,interaction:e.interaction,send:e.send})),discordJson({content:`Answer received.`,ephemeral:!0})}async function dispatchCommand(e){let t=commandInteractionMessage(e.interaction),n=formatDiscordContextBlock({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.send({message:t,context:[n,...r]},{auth:e.result.auth,continuationToken:discordContinuationToken(e.interaction.channelId,e.interaction.id),state:e.state})}catch(e){log.error(`command delivery failed`,{error:e})}}async function dispatchInputResponses(e){try{await e.send({inputResponses:e.inputResponses},{auth:null,continuationToken:discordContinuationToken(e.interaction.channelId,e.conversationId),state:stateFromInteraction(e.interaction,{conversationId:e.conversationId,hasMessageAnchor:!0,initialResponseSent:!0})})}catch(e){log.error(`interaction response delivery failed`,{error:e})}}function stateFromInteraction(e,t){return{applicationId:e.applicationId,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,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]}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{discordChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import"#public/channels/discord/verify.js";import{callDiscordApi,createDiscordFollowupMessage,discordContinuationToken,editDiscordOriginalResponse,sendDiscordChannelMessage,splitDiscordMessageContent,triggerDiscordTypingIndicator}from"#public/channels/discord/api.js";import{buildFreeformModalResponse,deriveComponentInputResponses,deriveModalInputResponses,isDiscordFreeformComponent}from"#public/channels/discord/hitl.js";import{defaultEvents,defaultOnCommand}from"#public/channels/discord/defaults.js";import{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{verifyDiscordInbound}from"#public/channels/discord/verifyInbound.js";const log=createLogger(`discord.channel`);function discordChannel(e={}){let t=e.onCommand??defaultOnCommand,a={...defaultEvents,...e.events};return defineChannel({kindHint:`discord`,state:initialDiscordState(),context(t,n){return rebuildDiscordContext(t,n,e)},routes:[POST(e.route??`/ash/v1/discord`,async(r,{send:i,waitUntil:a})=>{let o=await verifyDiscordInbound(r,e.credentials);if(o===null)return new Response(`unauthorized`,{status:401});let s;try{s=parseJsonObject(JSON.parse(o))}catch(e){return log.warn(`inbound Discord body is not valid JSON`,{error:e}),discordJson({content:`invalid request`,ephemeral:!0})}if(s.type===DISCORD_INTERACTION_TYPE.PING)return discordJson({type:DISCORD_INTERACTION_RESPONSE_TYPE.PONG});let c=parseDiscordInteraction(s);return c===null?discordJson({content:`Unsupported Discord interaction.`,ephemeral:!0}):handleInteraction({config:e,interaction:c,onCommand:t,send:i,waitUntil:a})})],async receive(t,{send:n}){let r=t.target,i=readString(r.channelId);if(!i)throw Error(`discordChannel().receive requires target.channelId.`);let a=readString(r.conversationId),o=r.initialMessage;if(o!==void 0&&a!==void 0)throw Error("discordChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=a??``,l=a!==void 0;if(o!==void 0){let t=await buildDiscordHandle({config:e,state:{...initialDiscordState(),channelId:i}}).sendChannelMessage(o);c=t.id,l=t.id.length>0}return n(t.message,{auth:t.auth,continuationToken:discordContinuationToken(i,c),state:{applicationId:null,channelId:i,conversationId:c||null,guildId:null,hasMessageAnchor:l,initialResponseSent:!0,interactionToken:null}})},events:a})}function rebuildDiscordContext(e,t,n){return{discord:buildDiscordHandle({config:n,session:t,state:e}),state:e}}function buildDiscordHandle(e){let n=e.config.api,r=e.state,i=mergeCredentials(e.config.credentials,r);function anchor(t){!t.id||r.hasMessageAnchor||(r.conversationId=t.id,r.hasMessageAnchor=!0,r.channelId&&e.session?.setContinuationToken(discordContinuationToken(r.channelId,t.id)))}async function sendViaChannel(e){let t=r.channelId??``;if(!t)throw Error(`discordChannel: missing channel id for outbound message.`);let a=await sendDiscordChannelMessage({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,channelId:t});return anchor(a),a}async function editOriginal(e){let t=r.interactionToken??``;if(!t)throw Error(`discordChannel: missing interaction token for original response edit.`);let a=await editDiscordOriginalResponse({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,interactionToken:t});return r.initialResponseSent=!0,anchor(a),a}async function followup(e){let t=r.interactionToken??``;if(!t)throw Error(`discordChannel: missing interaction token for followup message.`);let a=await createDiscordFollowupMessage({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,interactionToken:t});return anchor(a),a}async function startTyping(){let e=r.channelId??``;if(e)try{await triggerDiscordTypingIndicator({apiBaseUrl:n?.apiBaseUrl,credentials:i,fetch:n?.fetch,channelId:e})}catch(n){logError(log,`Discord typing indicator failed — swallowed`,n,{channelId:e})}}return{applicationId:r.applicationId??void 0,channelId:r.channelId??``,conversationId:r.conversationId??``,guildId:r.guildId??void 0,interactionToken:r.interactionToken??void 0,request(e,t,r){return callDiscordApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:r?.botAuth===!0?i.botToken:void 0,fetch:n?.fetch,method:r?.method,path:e})},async post(e){let t=expandPostBodies(normalizePostInput(e)),n;for(let e of t){let t=await postOne({body:e,editOriginal,followup,sendViaChannel,state:r});n===void 0&&(n=t)}return n??{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){return e.interaction.type===DISCORD_INTERACTION_TYPE.APPLICATION_COMMAND?handleCommandInteraction({config:e.config,interaction:e.interaction,onCommand:e.onCommand,send:e.send,waitUntil:e.waitUntil}):e.interaction.type===DISCORD_INTERACTION_TYPE.MESSAGE_COMPONENT?handleComponentInteraction({interaction:e.interaction,send:e.send,waitUntil:e.waitUntil}):handleModalSubmitInteraction({interaction:e.interaction,send:e.send,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,send:e.send,state:t})),discordDeferredJson(r.ephemeral===!0))}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=deriveComponentInputResponses(e.interaction);return t.length>0&&e.waitUntil(dispatchInputResponses({conversationId:e.interaction.messageId,inputResponses:t,interaction:e.interaction,send:e.send})),discordJsonBody({type:DISCORD_INTERACTION_RESPONSE_TYPE.DEFERRED_UPDATE_MESSAGE})}function handleModalSubmitInteraction(e){let t=deriveModalInputResponses(e.interaction);return t.length>0&&e.waitUntil(dispatchInputResponses({conversationId:e.interaction.messageId??e.interaction.id,inputResponses:t,interaction:e.interaction,send:e.send})),discordJson({content:`Answer received.`,ephemeral:!0})}async function dispatchCommand(e){let t=commandInteractionMessage(e.interaction),n=formatDiscordContextBlock({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.send({message:t,context:[n,...r]},{auth:e.result.auth,continuationToken:discordContinuationToken(e.interaction.channelId,e.interaction.id),state:e.state})}catch(e){log.error(`command delivery failed`,{error:e})}}async function dispatchInputResponses(e){try{await e.send({inputResponses:e.inputResponses},{auth:null,continuationToken:discordContinuationToken(e.interaction.channelId,e.conversationId),state:stateFromInteraction(e.interaction,{conversationId:e.conversationId,hasMessageAnchor:!0,initialResponseSent:!0})})}catch(e){log.error(`interaction response delivery failed`,{error:e})}}function stateFromInteraction(e,t){return{applicationId:e.applicationId,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,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]}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{discordChannel};
@@ -1,5 +1,5 @@
1
1
  export type { ModelMessage } from "ai";
2
- export { discordChannel, type DiscordChannel, type DiscordChannelConfig, type DiscordChannelCredentials, type DiscordChannelEvents, type DiscordChannelState, type DiscordCommandResult, type DiscordCommandResultOrPromise, type DiscordContext, type DiscordEventContext, type DiscordHandle, type DiscordReceiveArgs, type DiscordRequestOptions, } from "#public/channels/discord/discordChannel.js";
2
+ export { discordChannel, type DiscordChannel, type DiscordChannelConfig, type DiscordChannelCredentials, type DiscordChannelEvents, type DiscordChannelState, type DiscordCommandResult, type DiscordCommandResultOrPromise, type DiscordContext, type DiscordEventContext, type DiscordHandle, type DiscordReceiveTarget, type DiscordRequestOptions, } from "#public/channels/discord/discordChannel.js";
3
3
  export { callDiscordApi, createDiscordFollowupMessage, discordContinuationToken, DISCORD_MESSAGE_CONTENT_MAX_LENGTH, DISCORD_NO_MENTIONS, editDiscordOriginalResponse, resolveDiscordApplicationId, resolveDiscordBotToken, resolveDiscordPublicKey, sendDiscordChannelMessage, splitDiscordMessageContent, triggerDiscordTypingIndicator, type DiscordApiOptions, type DiscordApiResponse, type DiscordApplicationId, type DiscordBotToken, type DiscordCredentials, type DiscordFetch, type DiscordMessageBody, type DiscordPostedMessage, } from "#public/channels/discord/api.js";
4
4
  export { DISCORD_EPHEMERAL_MESSAGE_FLAG, DISCORD_INTERACTION_RESPONSE_TYPE, DISCORD_INTERACTION_TYPE, commandInteractionMessage, formatDiscordContextBlock, parseDiscordInteraction, type DiscordCommandInteraction, type DiscordCommandOption, type DiscordComponentInteraction, type DiscordInboundContext, type DiscordInteraction, type DiscordInteractionBase, type DiscordMember, type DiscordModalSubmitInteraction, type DiscordUser, } from "#public/channels/discord/inbound.js";
5
5
  export { DISCORD_COMPONENT_TYPE, DISCORD_HITL_CUSTOM_ID_PREFIX, DISCORD_HITL_FREEFORM_CUSTOM_ID_PREFIX, DISCORD_HITL_FREEFORM_TEXT_INPUT_ID, buildFreeformModalResponse, deriveComponentInputResponses, deriveModalInputResponses, isDiscordFreeformComponent, renderInputRequestComponents, } from "#public/channels/discord/hitl.js";
@@ -1,5 +1,5 @@
1
1
  export type { ModelMessage } from "ai";
2
- export { slackChannel, type SlackApiResponse, type SlackBotToken, type SlackChannel, type SlackChannelConfig, type SlackChannelCredentials, type SlackChannelEvents, type SlackChannelState, type SlackContext, type SlackEventContext, type SlackHandle, type SlackInboundResult, type SlackInboundResultOrPromise, type SlackInstrumentationMetadata, type SlackInitialMessage, type SlackInteractionAction, type SlackMentionResult, type SlackMentionResultOrPromise, type SlackReceiveArgs, type SlackThread, type SlackWebhookVerifier, } from "#public/channels/slack/slackChannel.js";
2
+ export { slackChannel, type SlackApiResponse, type SlackBotToken, type SlackChannel, type SlackChannelConfig, type SlackChannelCredentials, type SlackChannelEvents, type SlackChannelState, type SlackContext, type SlackEventContext, type SlackHandle, type SlackInboundResult, type SlackInboundResultOrPromise, type SlackInstrumentationMetadata, type SlackInitialMessage, type SlackInteractionAction, type SlackMentionResult, type SlackMentionResultOrPromise, type SlackReceiveTarget, type SlackThread, type SlackWebhookVerifier, } from "#public/channels/slack/slackChannel.js";
3
3
  export type { SlackAttachment, SlackAuthor, SlackInboundContext, SlackMessage, } from "#public/channels/slack/inbound.js";
4
4
  export { slackContinuationToken, type SlackPostInput, type SlackPostedMessage, type SlackThreadMessage, type SlackUploadFilesOptions, type SlackUploadFilesResult, } from "#public/channels/slack/api.js";
5
5
  export { defaultSlackAuth } from "#public/channels/slack/defaults.js";
@@ -99,7 +99,8 @@ export interface SlackChannelCredentials {
99
99
  */
100
100
  readonly webhookVerifier?: SlackWebhookVerifier;
101
101
  }
102
- export interface SlackReceiveArgs {
102
+ /** Target accepted by `receive(slack, { target })` for proactive sessions. */
103
+ export interface SlackReceiveTarget {
103
104
  readonly channelId: string;
104
105
  readonly threadTs?: string;
105
106
  /**
@@ -113,7 +114,7 @@ export interface SlackReceiveArgs {
113
114
  }
114
115
  /**
115
116
  * Pre-agent post issued by `slackChannel().receive` when the caller
116
- * provides `args.initialMessage`. The shape mirrors `ctx.thread.post`'s
117
+ * provides `target.initialMessage`. The shape mirrors `ctx.thread.post`'s
117
118
  * card variant so the same `Card({...})` construction can be reused.
118
119
  */
119
120
  export interface SlackInitialMessage {
@@ -288,7 +289,7 @@ export interface SlackChannelConfig {
288
289
  * default-export a `slackChannel(...)` call under `declaration: true`
289
290
  * without TypeScript falling back to an internal path for `Channel`.
290
291
  */
291
- export interface SlackChannel extends Channel<SlackChannelState, SlackReceiveArgs, SlackInstrumentationMetadata> {
292
+ export interface SlackChannel extends Channel<SlackChannelState, SlackReceiveTarget, SlackInstrumentationMetadata> {
292
293
  }
293
294
  /**
294
295
  * Slack channel factory. Wires up the Slack webhook route, mention
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{buildSlackBinding,slackContinuationToken}from"#public/channels/slack/api.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent}from"#public/channels/slack/inbound.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),l=createSlackFetchFile({botToken:e.credentials?.botToken}),u=e.onAppMention??defaultOnAppMention,d=e.onDirectMessage??defaultOnDirectMessage,f={...defaultEvents,...e.events,"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler()};return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,pendingAuthMessageTs:{}},fetchFile:l,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{send:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials);return a===null?new Response(`unauthorized`,{status:401}):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(a,{send:r,waitUntil:i},{config:e}):handleEventPost({body:a,send:r,waitUntil:i,onAppMention:u,onDirectMessage:d,uploadPolicy:t,credentials:e.credentials})})],async receive(t,{send:n}){let r=t.args,i=r.channelId;if(!i||typeof i!=`string`)throw Error(`slackChannel().receive requires args.channelId.`);let o=typeof r.threadTs==`string`?r.threadTs:``,s=r.initialMessage;if(s&&o.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=o;if(s){let{thread:t}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:i,threadTs:``,teamId:void 0}),n={card:s.card};s.fallbackText!==void 0&&(n.fallbackText=s.fallbackText),c=(await t.post(n)).id}return n(t.message,{auth:t.auth,continuationToken:slackContinuationToken(i,c),state:{channelId:i,threadTs:c||null,teamId:null,triggeringUserId:null}})},events:f})}async function handleEventPost(e){let t;try{t=JSON.parse(e.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(typeof t.challenge==`string`)return new Response(t.challenge,{status:200,headers:{"content-type":`text/plain`}});let n=parseAppMentionEvent(t);if(n)return e.waitUntil(dispatchInboundMessage({kind:`app_mention`,message:n,handler:e.onAppMention,send:e.send,uploadPolicy:e.uploadPolicy,credentials:e.credentials})),new Response(`ok`);let r=parseDirectMessageEvent(t);return r&&e.waitUntil(dispatchInboundMessage({kind:`direct_message`,message:r,handler:e.onDirectMessage,send:e.send,uploadPolicy:e.uploadPolicy,credentials:e.credentials})),new Response(`ok`)}async function verifyInbound(e,t){try{return await verifySlackRequest(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function dispatchInboundMessage(e){let{message:n,kind:r}=e,{thread:i,slack:o}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:n.channelId,threadTs:n.threadTs,teamId:n.teamId}),s={thread:i,slack:o},c;try{c=await e.handler(s,n)}catch(e){logError(log,`${r} handler failed`,e,{channelId:n.channelId});return}if(c!=null)try{let t=await collectInboundFileParts({mention:n,thread:i,policy:e.uploadPolicy}),r=buildSlackTurnMessage(n.markdown,t),a={channelId:n.channelId,fullName:n.author?.fullName,teamId:n.teamId,threadTs:n.threadTs,userId:n.author?.userId??``,userName:n.author?.userName},o=c.context??[];await e.send({message:r,context:[formatSlackContextBlock(a),...o]},{auth:c.auth,continuationToken:slackContinuationToken(n.channelId,n.threadTs),state:{channelId:n.channelId,threadTs:n.threadTs,teamId:n.teamId??null,triggeringUserId:a.userId||null}})}catch(e){logError(log,`${r} delivery failed`,e,{channelId:n.channelId})}}export{slackChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{buildSlackBinding,slackContinuationToken}from"#public/channels/slack/api.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent}from"#public/channels/slack/inbound.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),l=createSlackFetchFile({botToken:e.credentials?.botToken}),u=e.onAppMention??defaultOnAppMention,d=e.onDirectMessage??defaultOnDirectMessage,f={...defaultEvents,...e.events,"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler()};return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,pendingAuthMessageTs:{}},fetchFile:l,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{send:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials);return a===null?new Response(`unauthorized`,{status:401}):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(a,{send:r,waitUntil:i},{config:e}):handleEventPost({body:a,send:r,waitUntil:i,onAppMention:u,onDirectMessage:d,uploadPolicy:t,credentials:e.credentials})})],async receive(t,{send:n}){let r=t.target,i=r.channelId;if(!i||typeof i!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let o=typeof r.threadTs==`string`?r.threadTs:``,s=r.initialMessage;if(s&&o.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=o;if(s){let{thread:t}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:i,threadTs:``,teamId:void 0}),n={card:s.card};s.fallbackText!==void 0&&(n.fallbackText=s.fallbackText),c=(await t.post(n)).id}return n(t.message,{auth:t.auth,continuationToken:slackContinuationToken(i,c),state:{channelId:i,threadTs:c||null,teamId:null,triggeringUserId:null}})},events:f})}async function handleEventPost(e){let t;try{t=JSON.parse(e.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(typeof t.challenge==`string`)return new Response(t.challenge,{status:200,headers:{"content-type":`text/plain`}});let n=parseAppMentionEvent(t);if(n)return e.waitUntil(dispatchInboundMessage({kind:`app_mention`,message:n,handler:e.onAppMention,send:e.send,uploadPolicy:e.uploadPolicy,credentials:e.credentials})),new Response(`ok`);let r=parseDirectMessageEvent(t);return r&&e.waitUntil(dispatchInboundMessage({kind:`direct_message`,message:r,handler:e.onDirectMessage,send:e.send,uploadPolicy:e.uploadPolicy,credentials:e.credentials})),new Response(`ok`)}async function verifyInbound(e,t){try{return await verifySlackRequest(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function dispatchInboundMessage(e){let{message:n,kind:r}=e,{thread:i,slack:o}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:n.channelId,threadTs:n.threadTs,teamId:n.teamId}),s={thread:i,slack:o},c;try{c=await e.handler(s,n)}catch(e){logError(log,`${r} handler failed`,e,{channelId:n.channelId});return}if(c!=null)try{let t=await collectInboundFileParts({mention:n,thread:i,policy:e.uploadPolicy}),r=buildSlackTurnMessage(n.markdown,t),a={channelId:n.channelId,fullName:n.author?.fullName,teamId:n.teamId,threadTs:n.threadTs,userId:n.author?.userId??``,userName:n.author?.userName},o=c.context??[];await e.send({message:r,context:[formatSlackContextBlock(a),...o]},{auth:c.auth,continuationToken:slackContinuationToken(n.channelId,n.threadTs),state:{channelId:n.channelId,threadTs:n.threadTs,teamId:n.teamId??null,triggeringUserId:a.userId||null}})}catch(e){logError(log,`${r} delivery failed`,e,{channelId:n.channelId})}}export{slackChannel};
@@ -1,5 +1,5 @@
1
1
  export type { ModelMessage } from "ai";
2
- export { teamsChannel, type TeamsChannel, type TeamsChannelConfig, type TeamsChannelCredentials, type TeamsChannelEvents, type TeamsChannelState, type TeamsContext, type TeamsEventContext, type TeamsHandle, type TeamsInboundResult, type TeamsInboundResultOrPromise, type TeamsInvokeResult, type TeamsInvokeResultOrPromise, type TeamsReceiveArgs, type TeamsRequestOptions, type TeamsThread, } from "#public/channels/teams/teamsChannel.js";
2
+ export { teamsChannel, type TeamsChannel, type TeamsChannelConfig, type TeamsChannelCredentials, type TeamsChannelEvents, type TeamsChannelState, type TeamsContext, type TeamsEventContext, type TeamsHandle, type TeamsInboundResult, type TeamsInboundResultOrPromise, type TeamsInvokeResult, type TeamsInvokeResultOrPromise, type TeamsReceiveTarget, type TeamsRequestOptions, type TeamsThread, } from "#public/channels/teams/teamsChannel.js";
3
3
  export { callTeamsConnectorApi, normalizeTeamsPostInput, replyToTeamsActivity, resolveTeamsAccessToken, resolveTeamsAppId, resolveTeamsAppPassword, resolveTeamsTenantId, sendTeamsActivity, splitTeamsMessageText, teamsContinuationToken, triggerTeamsTypingIndicator, updateTeamsActivity, TEAMS_MESSAGE_TEXT_MAX_LENGTH, type TeamsAccessTokenResult, type TeamsApiOptions, type TeamsApiResponse, type TeamsAppId, type TeamsAppPassword, type TeamsAttachment, type TeamsChannelAccount, type TeamsCredentials, type TeamsFetch, type TeamsMention, type TeamsMessageBody, type TeamsOutboundActivity, type TeamsPostedActivity, type TeamsTenantId, type TeamsTokenProvider, } from "#public/channels/teams/api.js";
4
4
  export { formatTeamsContextBlock, isTeamsPersonalMessage, parseTeamsActivity, teamsThreadRootActivityId, type TeamsActivity, type TeamsActivityBase, type TeamsConversationAccount, type TeamsConversationScope, type TeamsConversationUpdateActivity, type TeamsInboundContext, type TeamsInvokeActivity, type TeamsMessageActivity, } from "#public/channels/teams/inbound.js";
5
5
  export { deriveTeamsInputResponses, isTeamsInputResponseActivity, renderAnsweredInputRequestMessage, renderInputRequestAttachment, renderInputRequestMessage, teamsInvokeResponse, TEAMS_ADAPTIVE_CARD_CONTENT_TYPE, TEAMS_HITL_CHOICE_INPUT_ID, TEAMS_HITL_DATA_KEY, TEAMS_HITL_FREEFORM_INPUT_ID, } from "#public/channels/teams/hitl.js";
@@ -57,8 +57,8 @@ export interface TeamsChannelCredentials extends TeamsCredentials {
57
57
  */
58
58
  readonly webhookVerifier?: TeamsWebhookVerifier;
59
59
  }
60
- /** Arguments accepted by `receive(teams, args)` for proactive sessions. */
61
- export interface TeamsReceiveArgs {
60
+ /** Target accepted by `receive(teams, { target })` for proactive sessions. */
61
+ export interface TeamsReceiveTarget {
62
62
  readonly channelId?: string;
63
63
  readonly conversationId: string;
64
64
  readonly conversationType?: string;
@@ -156,7 +156,7 @@ export interface TeamsRequestOptions {
156
156
  readonly method?: "DELETE" | "GET" | "POST" | "PUT";
157
157
  }
158
158
  /** Concrete return type of {@link teamsChannel}. */
159
- export interface TeamsChannel extends Channel<TeamsChannelState, TeamsReceiveArgs> {
159
+ export interface TeamsChannel extends Channel<TeamsChannelState, TeamsReceiveTarget> {
160
160
  }
161
161
  /** Teams channel factory for Bot Framework Activities and proactive messages. */
162
162
  export declare function teamsChannel(config?: TeamsChannelConfig): TeamsChannel;
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{callTeamsConnectorApi,normalizeTeamsPostInput,replyToTeamsActivity,sendTeamsActivity,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{deriveTeamsInputResponses,isTeamsInputResponseActivity,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{formatTeamsContextBlock,parseTeamsActivity,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{defaultEvents,defaultOnMessage,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),a=e.onMessage??defaultOnMessage,o={...defaultEvents,...e.events};return defineChannel({kindHint:`teams`,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t),context(t,n){return rebuildTeamsContext(t,n,e)},routes:[POST(e.route??`/ash/v1/teams`,async(r,{send:i,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Teams body is not valid JSON`,{error:e}),teamsOk()}let l=parseTeamsActivity(c);return l===null?teamsOk():l.type===`message`?(o(dispatchMessage({activity:l,config:e,filesPolicy:t,onMessage:a,send:i})),teamsOk()):l.type===`invoke`?handleInvoke({activity:l,config:e,send:i,waitUntil:o}):teamsOk()})],async receive(t,{send:n}){let r=t.args,i=readString(r.serviceUrl),a=readString(r.conversationId);if(!i||!a)throw Error(`teamsChannel().receive requires args.serviceUrl and args.conversationId.`);let o=readString(r.conversationType)??null,s=readString(r.replyToActivityId)??null,c=r.initialMessage;if(c!==void 0&&s!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let u={...initialTeamsState(),channelId:readString(r.channelId)??null,conversationId:a,conversationType:o,replyToActivityId:s,serviceUrl:i,teamId:readString(r.teamId)??null,tenantId:readString(r.tenantId)??null};if(c!==void 0){let t=await buildTeamsBinding({config:e,state:u}).thread.post(c);o!==`personal`&&t.id&&(s=t.id,u.replyToActivityId=t.id)}return n(t.message,{auth:t.auth,continuationToken:teamsContinuationToken({conversationId:a,replyToActivityId:s,tenantId:u.tenantId}),state:u})},events:o})}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,state:e}}function buildTeamsBinding(e){let n=buildTeamsHandle(e);return{teams:n,thread:{mentionUser:teamsMentionUser,post(e){return n.sendActivity(e)},async startTyping(){try{await n.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,t){return n.updateActivity(e,t)}}}}function buildTeamsHandle(e){let t=e.state,n=e.config.api,r=e.config.credentials;function requireAddress(){let e=t.conversationId??``,n=t.serviceUrl??``;if(!e||!n)throw Error(`teamsChannel: missing serviceUrl or conversationId for outbound message.`);return{conversationId:e,serviceUrl:n}}function anchor(n){if(!n.id||t.replyToActivityId||t.conversationType===`personal`)return;t.replyToActivityId=n.id;let r=t.conversationId;r&&e.session?.setContinuationToken(teamsContinuationToken({conversationId:r,replyToActivityId:n.id,tenantId:t.tenantId}))}async function send(e){let i=requireAddress(),a=buildOutboundActivity(t,e),o=t.replyToActivityId===null?await sendTeamsActivity({...n,body:a,credentials:r,conversationId:i.conversationId,serviceUrl:i.serviceUrl}):await replyToTeamsActivity({...n,body:a,credentials:r,activityId:t.replyToActivityId,conversationId:i.conversationId,serviceUrl:i.serviceUrl});return anchor(o),o}return{channelId:t.channelId??void 0,conversationId:t.conversationId??``,conversationType:t.conversationType??void 0,replyToActivityId:t.replyToActivityId??void 0,serviceUrl:t.serviceUrl??``,teamId:t.teamId??void 0,tenantId:t.tenantId??void 0,request(e,t,i){let o=requireAddress();return callTeamsConnectorApi({...n,body:t,credentials:r,method:i?.method,path:e,serviceUrl:o.serviceUrl})},sendActivity:send,replyToActivity(e){let i=requireAddress(),a=t.replyToActivityId??``;if(!a)throw Error(`teamsChannel: missing reply activity id.`);return replyToTeamsActivity({...n,body:buildOutboundActivity(t,e),credentials:r,activityId:a,conversationId:i.conversationId,serviceUrl:i.serviceUrl})},updateActivity(e,i){let a=requireAddress();return updateTeamsActivity({...n,body:buildOutboundActivity(t,i),credentials:r,activityId:e,conversationId:a.conversationId,serviceUrl:a.serviceUrl})},async startTyping(){let e=requireAddress();await triggerTeamsTypingIndicator({...n,credentials:r,conversationId:e.conversationId,serviceUrl:e.serviceUrl})}}}async function verifyInbound(e,t){try{return await verifyTeamsRequest(e,{appId:t?.webhookVerifier?void 0:t?.appId,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`teams inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r;try{r=await e.onMessage(n,e.activity)}catch(e){log.error(`Teams message handler failed`,{error:e});return}if(r==null)return;let i=collectTeamsFileParts(e.activity.attachments,e.filesPolicy),a=buildTeamsTurnMessage(e.activity.text,i),o={activityId:e.activity.id,channelId:e.activity.teamsChannelId,conversationId:e.activity.conversation.id,conversationType:e.activity.conversationType,scope:e.activity.scope,teamId:e.activity.teamId,tenantId:e.activity.tenantId,userId:e.activity.from.id,userName:e.activity.from.name},s=r.context??[];try{await e.send({message:a,context:[formatTeamsContextBlock(o),...s]},{auth:r.auth,continuationToken:stateToken(t),state:t})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){if(isTeamsInputResponseActivity(e.activity))return e.waitUntil(dispatchInputResponses({activity:e.activity,send:e.send})),Response.json(teamsInvokeResponse());if(e.config.onInvoke===void 0)return teamsOk();let t=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),n=await e.config.onInvoke(t,e.activity);return n instanceof Response?n:n&&typeof n==`object`?Response.json(n):teamsOk()}async function dispatchInputResponses(e){let t=deriveTeamsInputResponses(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity);try{await e.send({inputResponses:t},{auth:null,continuationToken:stateToken(n),state:n})}catch(e){log.error(`Teams input response delivery failed`,{error:e})}}function stateFromActivity(e){return{bot:e.recipient,channelId:e.teamsChannelId??null,conversationId:e.conversation.id,conversationType:e.conversationType??e.scope,pendingAuthActivityId:null,replyToActivityId:teamsThreadRootActivityId(e),serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,pendingAuthActivityId:null,replyToActivityId:null,serviceUrl:null,teamId:null,tenantId:null,triggeringUser:null}}function stateToken(e){let t=e.conversationId??``;if(!t)throw Error(`teamsChannel: missing conversation id.`);return teamsContinuationToken({conversationId:t,replyToActivityId:e.replyToActivityId,tenantId:e.tenantId})}function buildOutboundActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)return t;let n=normalizeTeamsPostInput(t),r=mergeChannelData(e,n.channelData);return{...n,channelData:r,conversation:e.conversationId?{id:e.conversationId}:void 0,from:e.bot??void 0,replyToId:e.replyToActivityId??void 0,type:`message`}}function mergeChannelData(e,t){let r={...t};return e.tenantId&&(r.tenant={id:e.tenantId}),e.teamId&&(r.team={id:e.teamId}),e.channelId&&(r.channel={id:e.channelId}),Object.keys(r).length>0?parseJsonObject(r):void 0}function teamsOk(){return new Response(`ok`,{status:200})}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{teamsChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{callTeamsConnectorApi,normalizeTeamsPostInput,replyToTeamsActivity,sendTeamsActivity,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{deriveTeamsInputResponses,isTeamsInputResponseActivity,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{formatTeamsContextBlock,parseTeamsActivity,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{defaultEvents,defaultOnMessage,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),a=e.onMessage??defaultOnMessage,o={...defaultEvents,...e.events};return defineChannel({kindHint:`teams`,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t),context(t,n){return rebuildTeamsContext(t,n,e)},routes:[POST(e.route??`/ash/v1/teams`,async(r,{send:i,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Teams body is not valid JSON`,{error:e}),teamsOk()}let l=parseTeamsActivity(c);return l===null?teamsOk():l.type===`message`?(o(dispatchMessage({activity:l,config:e,filesPolicy:t,onMessage:a,send:i})),teamsOk()):l.type===`invoke`?handleInvoke({activity:l,config:e,send:i,waitUntil:o}):teamsOk()})],async receive(t,{send:n}){let r=t.target,i=readString(r.serviceUrl),a=readString(r.conversationId);if(!i||!a)throw Error(`teamsChannel().receive requires target.serviceUrl and target.conversationId.`);let o=readString(r.conversationType)??null,s=readString(r.replyToActivityId)??null,c=r.initialMessage;if(c!==void 0&&s!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let u={...initialTeamsState(),channelId:readString(r.channelId)??null,conversationId:a,conversationType:o,replyToActivityId:s,serviceUrl:i,teamId:readString(r.teamId)??null,tenantId:readString(r.tenantId)??null};if(c!==void 0){let t=await buildTeamsBinding({config:e,state:u}).thread.post(c);o!==`personal`&&t.id&&(s=t.id,u.replyToActivityId=t.id)}return n(t.message,{auth:t.auth,continuationToken:teamsContinuationToken({conversationId:a,replyToActivityId:s,tenantId:u.tenantId}),state:u})},events:o})}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,state:e}}function buildTeamsBinding(e){let n=buildTeamsHandle(e);return{teams:n,thread:{mentionUser:teamsMentionUser,post(e){return n.sendActivity(e)},async startTyping(){try{await n.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,t){return n.updateActivity(e,t)}}}}function buildTeamsHandle(e){let t=e.state,n=e.config.api,r=e.config.credentials;function requireAddress(){let e=t.conversationId??``,n=t.serviceUrl??``;if(!e||!n)throw Error(`teamsChannel: missing serviceUrl or conversationId for outbound message.`);return{conversationId:e,serviceUrl:n}}function anchor(n){if(!n.id||t.replyToActivityId||t.conversationType===`personal`)return;t.replyToActivityId=n.id;let r=t.conversationId;r&&e.session?.setContinuationToken(teamsContinuationToken({conversationId:r,replyToActivityId:n.id,tenantId:t.tenantId}))}async function send(e){let i=requireAddress(),a=buildOutboundActivity(t,e),o=t.replyToActivityId===null?await sendTeamsActivity({...n,body:a,credentials:r,conversationId:i.conversationId,serviceUrl:i.serviceUrl}):await replyToTeamsActivity({...n,body:a,credentials:r,activityId:t.replyToActivityId,conversationId:i.conversationId,serviceUrl:i.serviceUrl});return anchor(o),o}return{channelId:t.channelId??void 0,conversationId:t.conversationId??``,conversationType:t.conversationType??void 0,replyToActivityId:t.replyToActivityId??void 0,serviceUrl:t.serviceUrl??``,teamId:t.teamId??void 0,tenantId:t.tenantId??void 0,request(e,t,i){let o=requireAddress();return callTeamsConnectorApi({...n,body:t,credentials:r,method:i?.method,path:e,serviceUrl:o.serviceUrl})},sendActivity:send,replyToActivity(e){let i=requireAddress(),a=t.replyToActivityId??``;if(!a)throw Error(`teamsChannel: missing reply activity id.`);return replyToTeamsActivity({...n,body:buildOutboundActivity(t,e),credentials:r,activityId:a,conversationId:i.conversationId,serviceUrl:i.serviceUrl})},updateActivity(e,i){let a=requireAddress();return updateTeamsActivity({...n,body:buildOutboundActivity(t,i),credentials:r,activityId:e,conversationId:a.conversationId,serviceUrl:a.serviceUrl})},async startTyping(){let e=requireAddress();await triggerTeamsTypingIndicator({...n,credentials:r,conversationId:e.conversationId,serviceUrl:e.serviceUrl})}}}async function verifyInbound(e,t){try{return await verifyTeamsRequest(e,{appId:t?.webhookVerifier?void 0:t?.appId,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`teams inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r;try{r=await e.onMessage(n,e.activity)}catch(e){log.error(`Teams message handler failed`,{error:e});return}if(r==null)return;let i=collectTeamsFileParts(e.activity.attachments,e.filesPolicy),a=buildTeamsTurnMessage(e.activity.text,i),o={activityId:e.activity.id,channelId:e.activity.teamsChannelId,conversationId:e.activity.conversation.id,conversationType:e.activity.conversationType,scope:e.activity.scope,teamId:e.activity.teamId,tenantId:e.activity.tenantId,userId:e.activity.from.id,userName:e.activity.from.name},s=r.context??[];try{await e.send({message:a,context:[formatTeamsContextBlock(o),...s]},{auth:r.auth,continuationToken:stateToken(t),state:t})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){if(isTeamsInputResponseActivity(e.activity))return e.waitUntil(dispatchInputResponses({activity:e.activity,send:e.send})),Response.json(teamsInvokeResponse());if(e.config.onInvoke===void 0)return teamsOk();let t=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),n=await e.config.onInvoke(t,e.activity);return n instanceof Response?n:n&&typeof n==`object`?Response.json(n):teamsOk()}async function dispatchInputResponses(e){let t=deriveTeamsInputResponses(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity);try{await e.send({inputResponses:t},{auth:null,continuationToken:stateToken(n),state:n})}catch(e){log.error(`Teams input response delivery failed`,{error:e})}}function stateFromActivity(e){return{bot:e.recipient,channelId:e.teamsChannelId??null,conversationId:e.conversation.id,conversationType:e.conversationType??e.scope,pendingAuthActivityId:null,replyToActivityId:teamsThreadRootActivityId(e),serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,pendingAuthActivityId:null,replyToActivityId:null,serviceUrl:null,teamId:null,tenantId:null,triggeringUser:null}}function stateToken(e){let t=e.conversationId??``;if(!t)throw Error(`teamsChannel: missing conversation id.`);return teamsContinuationToken({conversationId:t,replyToActivityId:e.replyToActivityId,tenantId:e.tenantId})}function buildOutboundActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)return t;let n=normalizeTeamsPostInput(t),r=mergeChannelData(e,n.channelData);return{...n,channelData:r,conversation:e.conversationId?{id:e.conversationId}:void 0,from:e.bot??void 0,replyToId:e.replyToActivityId??void 0,type:`message`}}function mergeChannelData(e,t){let r={...t};return e.tenantId&&(r.tenant={id:e.tenantId}),e.teamId&&(r.team={id:e.teamId}),e.channelId&&(r.channel={id:e.channelId}),Object.keys(r).length>0?parseJsonObject(r):void 0}function teamsOk(){return new Response(`ok`,{status:200})}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{teamsChannel};
@@ -1,5 +1,5 @@
1
1
  export type { ModelMessage } from "ai";
2
- export { telegramChannel, type TelegramChannel, type TelegramChannelConfig, type TelegramChannelCredentials, type TelegramChannelEvents, type TelegramChannelState, type TelegramContext, type TelegramEventContext, type TelegramHandle, type TelegramInboundResult, type TelegramInboundResultOrPromise, type TelegramReceiveArgs, } from "#public/channels/telegram/telegramChannel.js";
2
+ export { telegramChannel, type TelegramChannel, type TelegramChannelConfig, type TelegramChannelCredentials, type TelegramChannelEvents, type TelegramChannelState, type TelegramContext, type TelegramEventContext, type TelegramHandle, type TelegramInboundResult, type TelegramInboundResultOrPromise, type TelegramReceiveTarget, } from "#public/channels/telegram/telegramChannel.js";
3
3
  export { callTelegramApi, answerTelegramCallbackQuery, downloadTelegramFile, editTelegramMessageReplyMarkup, getTelegramFile, resolveTelegramBotToken, sendTelegramChatAction, sendTelegramMessage, splitTelegramMessageText, telegramContinuationToken, TELEGRAM_MESSAGE_TEXT_MAX_LENGTH, type TelegramApiOptions, type TelegramApiResponse, type TelegramBotToken, type TelegramCredentials, type TelegramFetch, type TelegramMessageBody, type TelegramMessageResult, } from "#public/channels/telegram/api.js";
4
4
  export { formatTelegramContextBlock, parseTelegramUpdate, type TelegramAttachment, type TelegramCallbackQuery, type TelegramChat, type TelegramChatType, type TelegramInboundContext, type TelegramMessage, type TelegramMessageReference, type TelegramUpdate, type TelegramUser, } from "#public/channels/telegram/inbound.js";
5
5
  export { TELEGRAM_CALLBACK_RESPONSE_PREFIX, TELEGRAM_HITL_CALLBACK_PREFIX, TELEGRAM_REPLY_RESPONSE_PREFIX, isTelegramSyntheticResponse, registerTelegramFreeformPrompt, renderTelegramInputRequest, resolveTelegramInputResponses, telegramCallbackInputResponse, telegramReplyInputResponse, type TelegramHitlState, type TelegramInputRequestMessage, } from "#public/channels/telegram/hitl.js";
@@ -51,8 +51,8 @@ export interface TelegramChannelCredentials extends TelegramCredentials {
51
51
  /** Custom inbound webhook verifier for forwarded webhooks. */
52
52
  readonly webhookVerifier?: TelegramWebhookVerifier;
53
53
  }
54
- /** Arguments accepted by `receive(telegram, args)` for proactive sessions. */
55
- export interface TelegramReceiveArgs {
54
+ /** Target accepted by `receive(telegram, { target })` for proactive sessions. */
55
+ export interface TelegramReceiveTarget {
56
56
  readonly chatId: number | string;
57
57
  readonly conversationId?: number | string;
58
58
  readonly initialMessage?: string | TelegramMessageBody;
@@ -120,7 +120,7 @@ export interface TelegramHandle {
120
120
  }): Promise<TelegramApiResponse>;
121
121
  }
122
122
  /** Concrete return type of {@link telegramChannel}. */
123
- export interface TelegramChannel extends Channel<TelegramChannelState, TelegramReceiveArgs> {
123
+ export interface TelegramChannel extends Channel<TelegramChannelState, TelegramReceiveTarget> {
124
124
  }
125
125
  /** Telegram channel factory for webhook updates and proactive messages. */
126
126
  export declare function telegramChannel(config?: TelegramChannelConfig): TelegramChannel;
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{parseJsonObject}from"#shared/json.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},c=defineChannel({kindHint:`telegram`,state:initialTelegramState(e.botUsername),fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/ash/v1/telegram`,async(r,{send:a,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:n,send:a,uploadPolicy:t})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,send:a})),new Response(`ok`))})],async receive(t,{send:n}){let r=t.args,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires args.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readOptionalString(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=o;return s!==void 0&&(c=(await buildTelegramHandle({config:e,state:{...initialTelegramState(e.botUsername),chatId:i,messageThreadId:a??null}}).sendMessage(s)).id||void 0),n(t.message,{auth:t.auth,continuationToken:telegramContinuationToken({chatId:i,conversationId:c,messageThreadId:a}),state:{...initialTelegramState(e.botUsername),chatId:i,conversationId:c??null,messageThreadId:a??null}})},events:r});return attachTelegramDeliver(c),c}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let n=e.config.api,r=e.state,i=e.config.credentials;function anchor(t){!t.id||r.chatType===`private`||(r.conversationId=t.id,r.chatId&&e.session?.setContinuationToken(telegramContinuationToken({chatId:r.chatId,conversationId:t.id,messageThreadId:r.messageThreadId??void 0})))}async function sendOne(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for outbound message.`);let a=await sendTelegramMessage({apiBaseUrl:n?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??r.messageThreadId??void 0},credentials:i,fetch:n?.fetch,fileBaseUrl:n?.fileBaseUrl,chatId:t});return anchor(a),a}return{botUsername:r.botUsername??e.config.botUsername,chatId:r.chatId??``,chatType:r.chatType??void 0,conversationId:r.conversationId??void 0,messageThreadId:r.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:n?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:i,fetch:n?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:n?.apiBaseUrl,chatId:t,credentials:i,fetch:n?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},post(e){return postTelegramMessage(e,sendOne)},request(e,t){return callTelegramApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:i?.botToken,fetch:n?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let a=r.chatId??``;if(a)try{await sendTelegramChatAction({action:e,apiBaseUrl:n?.apiBaseUrl,chatId:a,credentials:i,fetch:n?.fetch,messageThreadId:r.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:a})}}}}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromMessage(e.message,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=buildTelegramTurnMessage(e.message,i),o=formatTelegramContextBlock({botUsername:e.config.botUsername,chatId:e.message.chat.id,chatTitle:e.message.chat.title,chatType:e.message.chat.type,messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),s=r.context??[],c=e.message.text||e.message.caption,l=e.message.replyToMessage?.from?.isBot===!0&&c.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:c})]:void 0;try{await e.send({inputResponses:l,message:a,context:[o,...s]},{auth:r.auth,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromCallbackQuery(e.query,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.send({inputResponses:[telegramCallbackInputResponse(e.query.data)]},{auth:null,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function stateFromMessage(e,t){let n=e.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:e.chat.id,chatType:e.chat.type,conversationId:n?null:conversationIdForMessage(e),messageThreadId:e.messageThreadId??null,triggeringUserId:e.from?.id??null}}function stateFromCallbackQuery(e,t){let n=e.message;if(!n)return{...initialTelegramState(t.botUsername),triggeringUserId:e.from.id};let r=n.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:n.chat.id,chatType:n.chat.type,conversationId:r?null:n.messageId,messageThreadId:n.messageThreadId??null,triggeringUserId:e.from.id}}function conversationIdForMessage(e){return e.replyToMessage?.from?.isBot===!0?e.replyToMessage.messageId:e.messageId}function continuationTokenFromState(e){return telegramContinuationToken({chatId:e.chatId??``,conversationId:e.chatType===`private`?void 0:e.conversationId??void 0,messageThreadId:e.messageThreadId??void 0})}function initialTelegramState(e){return{botUsername:e??null,chatId:null,chatType:null,conversationId:null,hitlCallbacks:{},messageThreadId:null,nextHitlCallbackId:0,pendingFreeformReplies:{},triggeringUserId:null}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}function readOptionalString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{parseJsonObject}from"#shared/json.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},c=defineChannel({kindHint:`telegram`,state:initialTelegramState(e.botUsername),fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/ash/v1/telegram`,async(r,{send:a,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:n,send:a,uploadPolicy:t})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,send:a})),new Response(`ok`))})],async receive(t,{send:n}){let r=t.target,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires target.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readOptionalString(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=o;return s!==void 0&&(c=(await buildTelegramHandle({config:e,state:{...initialTelegramState(e.botUsername),chatId:i,messageThreadId:a??null}}).sendMessage(s)).id||void 0),n(t.message,{auth:t.auth,continuationToken:telegramContinuationToken({chatId:i,conversationId:c,messageThreadId:a}),state:{...initialTelegramState(e.botUsername),chatId:i,conversationId:c??null,messageThreadId:a??null}})},events:r});return attachTelegramDeliver(c),c}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let n=e.config.api,r=e.state,i=e.config.credentials;function anchor(t){!t.id||r.chatType===`private`||(r.conversationId=t.id,r.chatId&&e.session?.setContinuationToken(telegramContinuationToken({chatId:r.chatId,conversationId:t.id,messageThreadId:r.messageThreadId??void 0})))}async function sendOne(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for outbound message.`);let a=await sendTelegramMessage({apiBaseUrl:n?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??r.messageThreadId??void 0},credentials:i,fetch:n?.fetch,fileBaseUrl:n?.fileBaseUrl,chatId:t});return anchor(a),a}return{botUsername:r.botUsername??e.config.botUsername,chatId:r.chatId??``,chatType:r.chatType??void 0,conversationId:r.conversationId??void 0,messageThreadId:r.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:n?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:i,fetch:n?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:n?.apiBaseUrl,chatId:t,credentials:i,fetch:n?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},post(e){return postTelegramMessage(e,sendOne)},request(e,t){return callTelegramApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:i?.botToken,fetch:n?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let a=r.chatId??``;if(a)try{await sendTelegramChatAction({action:e,apiBaseUrl:n?.apiBaseUrl,chatId:a,credentials:i,fetch:n?.fetch,messageThreadId:r.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:a})}}}}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromMessage(e.message,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=buildTelegramTurnMessage(e.message,i),o=formatTelegramContextBlock({botUsername:e.config.botUsername,chatId:e.message.chat.id,chatTitle:e.message.chat.title,chatType:e.message.chat.type,messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),s=r.context??[],c=e.message.text||e.message.caption,l=e.message.replyToMessage?.from?.isBot===!0&&c.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:c})]:void 0;try{await e.send({inputResponses:l,message:a,context:[o,...s]},{auth:r.auth,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromCallbackQuery(e.query,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.send({inputResponses:[telegramCallbackInputResponse(e.query.data)]},{auth:null,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function stateFromMessage(e,t){let n=e.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:e.chat.id,chatType:e.chat.type,conversationId:n?null:conversationIdForMessage(e),messageThreadId:e.messageThreadId??null,triggeringUserId:e.from?.id??null}}function stateFromCallbackQuery(e,t){let n=e.message;if(!n)return{...initialTelegramState(t.botUsername),triggeringUserId:e.from.id};let r=n.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:n.chat.id,chatType:n.chat.type,conversationId:r?null:n.messageId,messageThreadId:n.messageThreadId??null,triggeringUserId:e.from.id}}function conversationIdForMessage(e){return e.replyToMessage?.from?.isBot===!0?e.replyToMessage.messageId:e.messageId}function continuationTokenFromState(e){return telegramContinuationToken({chatId:e.chatId??``,conversationId:e.chatType===`private`?void 0:e.conversationId??void 0,messageThreadId:e.messageThreadId??void 0})}function initialTelegramState(e){return{botUsername:e??null,chatId:null,chatType:null,conversationId:null,hitlCallbacks:{},messageThreadId:null,nextHitlCallbackId:0,pendingFreeformReplies:{},triggeringUserId:null}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}function readOptionalString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
@@ -1,4 +1,4 @@
1
- export { twilioChannel, type TwilioAllowFrom, type TwilioChannel, type TwilioChannelConfig, type TwilioChannelCredentials, type TwilioChannelEvents, type TwilioChannelState, type TwilioContext, type TwilioEventContext, type TwilioHandle, type TwilioInboundResult, type TwilioInboundResultOrPromise, type TwilioInstrumentationMetadata, type TwilioMessagingConfig, type TwilioReceiveArgs, type TwilioSendMessageOptions, type TwilioVoiceConfig, type TwilioVoiceResult, type TwilioVoiceResultOrPromise, } from "#public/channels/twilio/twilioChannel.js";
1
+ export { twilioChannel, type TwilioAllowFrom, type TwilioChannel, type TwilioChannelConfig, type TwilioChannelCredentials, type TwilioChannelEvents, type TwilioChannelState, type TwilioContext, type TwilioEventContext, type TwilioHandle, type TwilioInboundResult, type TwilioInboundResultOrPromise, type TwilioInstrumentationMetadata, type TwilioMessagingConfig, type TwilioReceiveTarget, type TwilioSendMessageOptions, type TwilioVoiceConfig, type TwilioVoiceResult, type TwilioVoiceResultOrPromise, } from "#public/channels/twilio/twilioChannel.js";
2
2
  export { callTwilioApi, resolveTwilioAccountSid, sendTwilioMessage, twilioContinuationToken, updateTwilioCall, type TwilioAccountSid, type TwilioApiOptions, type TwilioApiResponse, type TwilioCredentials, type TwilioFetch, type TwilioSendMessageInput, type TwilioUpdateCallInput, } from "#public/channels/twilio/api.js";
3
3
  export type { TwilioInboundContext, TwilioTextMessage, TwilioVoiceCall, TwilioVoiceTranscription, } from "#public/channels/twilio/inbound.js";
4
4
  export { emptyTwilioResponse, escapeXml, gatherSpeechTwilioResponse, sayTwilioResponse, twimlResponse, type TwilioGatherTwimlOptions, } from "#public/channels/twilio/twiml.js";
@@ -43,8 +43,8 @@ export interface TwilioInstrumentationMetadata extends Record<string, unknown> {
43
43
  export interface TwilioChannelCredentials extends TwilioCredentials {
44
44
  readonly authToken?: TwilioAuthToken;
45
45
  }
46
- /** Arguments accepted by `receive(twilio, args)` for proactive phone-number sessions. */
47
- export interface TwilioReceiveArgs {
46
+ /** Target accepted by `receive(twilio, { target })` for proactive phone-number sessions. */
47
+ export interface TwilioReceiveTarget {
48
48
  readonly phoneNumber: string;
49
49
  /** Twilio sender included in the phone-pair continuation token. */
50
50
  readonly from?: string;
@@ -183,7 +183,7 @@ export interface TwilioSendMessageOptions {
183
183
  readonly statusCallbackUrl?: string;
184
184
  }
185
185
  /** Concrete return type of {@link twilioChannel}. */
186
- export interface TwilioChannel extends Channel<TwilioChannelState, TwilioReceiveArgs, TwilioInstrumentationMetadata> {
186
+ export interface TwilioChannel extends Channel<TwilioChannelState, TwilioReceiveTarget, TwilioInstrumentationMetadata> {
187
187
  }
188
188
  /** Twilio channel factory for SMS and speech-transcribed inbound calls. */
189
189
  export declare function twilioChannel(config: TwilioChannelConfig): TwilioChannel;
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{verifyTwilioRequest}from"#public/channels/twilio/verify.js";import{callTwilioApi,sendTwilioMessage,twilioContinuationToken,updateTwilioCall}from"#public/channels/twilio/api.js";import{emptyTwilioResponse,gatherSpeechTwilioResponse,sayTwilioResponse}from"#public/channels/twilio/twiml.js";import{defaultEvents,defaultOnText,defaultOnVoice,defaultOnVoiceTranscription}from"#public/channels/twilio/defaults.js";import{formatTwilioContextBlock,parseTwilioTextMessage,parseTwilioVoiceCall,parseTwilioVoiceTranscription}from"#public/channels/twilio/inbound.js";const log=createLogger(`twilio.channel`);function twilioChannel(e){assertAllowFromConfigured(e);let r=buildRoutes(e.route??`/ash/v1/twilio`),i=e.onText??defaultOnText,a=e.onVoice??defaultOnVoice,s=e.onVoiceTranscription??defaultOnVoiceTranscription,l={...defaultEvents,...e.events};return defineChannel({kindHint:`twilio`,state:{from:null,to:null,lastCallSid:null,lastMessageSid:null},metadata(e){return{from:e.from,lastCallSid:e.lastCallSid??null,lastMessageSid:e.lastMessageSid??null,to:e.to}},context(t,n){return rebuildTwilioContext(t,n,e)},routes:[POST(r.messages,async(t,{send:n,waitUntil:r})=>{let a=await verifyInbound(t,e);if(a===null)return new Response(`unauthorized`,{status:401});let o=parseTwilioTextMessage(a.params);return o?await isAllowed(o.from,e.allowFrom)?(r(dispatchText({config:e,message:o,onText:i,send:n})),emptyTwilioResponse()):new Response(`forbidden`,{status:403}):emptyTwilioResponse()}),POST(r.voice,async t=>{let n=await verifyInbound(t,e);if(n===null)return new Response(`unauthorized`,{status:401});let i=parseTwilioVoiceCall(n.params);if(!i)return sayTwilioResponse(`Missing caller information.`);if(!await isAllowed(i.from,e.allowFrom))return new Response(`forbidden`,{status:403});let o=await acceptVoiceCall({call:i,config:e,onVoice:a});if(o===null)return new Response(`forbidden`,{status:403});let s=o??{};return gatherSpeechTwilioResponse({actionUrl:await buildActionUrl(t,e,r.transcription),hints:s.hints??e.voice?.hints,language:s.language??e.voice?.language,profanityFilter:s.profanityFilter??e.voice?.profanityFilter,prompt:s.prompt??e.voice?.prompt??`Please say your message after the tone.`,speechModel:s.speechModel??e.voice?.speechModel,speechTimeout:s.speechTimeout??e.voice?.speechTimeout??`auto`,timeoutSeconds:s.timeoutSeconds??e.voice?.timeoutSeconds,voice:s.voice??e.voice?.voice})}),POST(r.transcription,async(t,{send:n,waitUntil:i})=>{let a=await verifyInbound(t,e);if(a===null)return new Response(`unauthorized`,{status:401});let o=parseTwilioVoiceTranscription(a.params);return o?await isAllowed(o.from,e.allowFrom)?(i(dispatchVoiceTranscription({config:e,onVoiceTranscription:s,send:n,transcription:o})),sayTwilioResponse(e.voice?.acknowledgement??`Thanks. I'll follow up by text.`)):new Response(`forbidden`,{status:403}):gatherSpeechTwilioResponse({actionUrl:await buildActionUrl(t,e,r.transcription),language:e.voice?.language,prompt:e.voice?.prompt??`Please say your message after the tone.`,speechTimeout:e.voice?.speechTimeout??`auto`,timeoutSeconds:e.voice?.timeoutSeconds})})],async receive(t,{send:n}){let r=readString(t.args.phoneNumber);if(!r)throw Error(`twilioChannel().receive requires args.phoneNumber.`);let i=readString(t.args.from)??e.messaging?.from??null;return n(t.message,{auth:t.auth,continuationToken:twilioContinuationToken(r,i??void 0),state:{from:r,lastCallSid:null,lastMessageSid:null,to:i}})},events:l})}function rebuildTwilioContext(e,t,n){return{state:e,twilio:buildTwilioHandle({callSid:e.lastCallSid??void 0,config:n,from:e.from??``,to:e.to??void 0})}}function buildTwilioHandle(e){let t=e.config.api,n=e.config.credentials,r=e.config.messaging?.from??e.to,o=e.config.messaging?.messagingServiceSid,c=e.config.messaging?.statusCallbackUrl;return{callSid:e.callSid,from:e.from,to:e.to,request(e,r){return callTwilioApi({apiBaseUrl:t?.apiBaseUrl,body:r,credentials:n,fetch:t?.fetch,path:e})},sendMessage(i,s){return sendTwilioMessage({apiBaseUrl:t?.apiBaseUrl,body:i,credentials:n,fetch:t?.fetch,from:s?.from??r,messagingServiceSid:s?.messagingServiceSid??o,statusCallbackUrl:s?.statusCallbackUrl??c,to:s?.to??e.from})},updateCall(e,r){return updateTwilioCall({apiBaseUrl:t?.apiBaseUrl,callSid:e,credentials:n,fetch:t?.fetch,twiml:r})}}}function buildRoutes(e){let t=e.endsWith(`/`)?e.slice(0,-1):e;return{messages:`${t}/messages`,transcription:`${t}/voice/transcription`,voice:`${t}/voice`}}function assertAllowFromConfigured(e){if(e?.allowFrom===void 0)throw Error(`twilioChannel requires allowFrom. Use allowFrom: "*" to allow all numbers.`)}async function verifyInbound(e,t){try{return await verifyTwilioRequest(e,{authToken:t.credentials?.authToken,webhookUrl:t.webhookUrl})}catch(e){return log.warn(`twilio inbound verification failed`,{error:e}),null}}async function dispatchText(e){let{message:t}=e,n={twilio:buildTwilioHandle({callSid:void 0,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onText(n,t)}catch(e){log.error(`text handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({channel:`text`,from:t.from,messageSid:t.messageSid,to:t.to});try{await e.send({message:t.body,context:[i]},{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:null,lastMessageSid:t.messageSid??null,to:t.to??null}})}catch(e){log.error(`text delivery failed`,{error:e})}}async function acceptVoiceCall(e){let{call:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})};try{return await e.onVoice(n,t)}catch(e){return log.error(`voice handler failed`,{error:e}),null}}async function dispatchVoiceTranscription(e){let{transcription:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onVoiceTranscription(n,t)}catch(e){log.error(`voice transcription handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({callSid:t.callSid,channel:`voice`,from:t.from,to:t.to});try{await e.send({message:t.text,context:[i]},{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:t.callSid??null,lastMessageSid:null,to:t.to??null}})}catch(e){log.error(`voice transcription delivery failed`,{error:e})}}async function isAllowed(e,t){let n=typeof t==`function`?await t():t;return n===`*`?!0:typeof n==`string`?n===e:n.includes(e)}async function buildActionUrl(e,t,n){let r=typeof t.publicBaseUrl==`function`?await t.publicBaseUrl(e):t.publicBaseUrl;if(r)return new URL(n,ensureTrailingSlash(r)).toString();let i=new URL(e.url);return i.pathname=n,i.search=``,i.toString()}function ensureTrailingSlash(e){return e.endsWith(`/`)?e:`${e}/`}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{twilioChannel};
1
+ import{createLogger}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{verifyTwilioRequest}from"#public/channels/twilio/verify.js";import{callTwilioApi,sendTwilioMessage,twilioContinuationToken,updateTwilioCall}from"#public/channels/twilio/api.js";import{emptyTwilioResponse,gatherSpeechTwilioResponse,sayTwilioResponse}from"#public/channels/twilio/twiml.js";import{defaultEvents,defaultOnText,defaultOnVoice,defaultOnVoiceTranscription}from"#public/channels/twilio/defaults.js";import{formatTwilioContextBlock,parseTwilioTextMessage,parseTwilioVoiceCall,parseTwilioVoiceTranscription}from"#public/channels/twilio/inbound.js";const log=createLogger(`twilio.channel`);function twilioChannel(e){assertAllowFromConfigured(e);let r=buildRoutes(e.route??`/ash/v1/twilio`),i=e.onText??defaultOnText,a=e.onVoice??defaultOnVoice,s=e.onVoiceTranscription??defaultOnVoiceTranscription,l={...defaultEvents,...e.events};return defineChannel({kindHint:`twilio`,state:{from:null,to:null,lastCallSid:null,lastMessageSid:null},metadata(e){return{from:e.from,lastCallSid:e.lastCallSid??null,lastMessageSid:e.lastMessageSid??null,to:e.to}},context(t,n){return rebuildTwilioContext(t,n,e)},routes:[POST(r.messages,async(t,{send:n,waitUntil:r})=>{let a=await verifyInbound(t,e);if(a===null)return new Response(`unauthorized`,{status:401});let o=parseTwilioTextMessage(a.params);return o?await isAllowed(o.from,e.allowFrom)?(r(dispatchText({config:e,message:o,onText:i,send:n})),emptyTwilioResponse()):new Response(`forbidden`,{status:403}):emptyTwilioResponse()}),POST(r.voice,async t=>{let n=await verifyInbound(t,e);if(n===null)return new Response(`unauthorized`,{status:401});let i=parseTwilioVoiceCall(n.params);if(!i)return sayTwilioResponse(`Missing caller information.`);if(!await isAllowed(i.from,e.allowFrom))return new Response(`forbidden`,{status:403});let o=await acceptVoiceCall({call:i,config:e,onVoice:a});if(o===null)return new Response(`forbidden`,{status:403});let s=o??{};return gatherSpeechTwilioResponse({actionUrl:await buildActionUrl(t,e,r.transcription),hints:s.hints??e.voice?.hints,language:s.language??e.voice?.language,profanityFilter:s.profanityFilter??e.voice?.profanityFilter,prompt:s.prompt??e.voice?.prompt??`Please say your message after the tone.`,speechModel:s.speechModel??e.voice?.speechModel,speechTimeout:s.speechTimeout??e.voice?.speechTimeout??`auto`,timeoutSeconds:s.timeoutSeconds??e.voice?.timeoutSeconds,voice:s.voice??e.voice?.voice})}),POST(r.transcription,async(t,{send:n,waitUntil:i})=>{let a=await verifyInbound(t,e);if(a===null)return new Response(`unauthorized`,{status:401});let o=parseTwilioVoiceTranscription(a.params);return o?await isAllowed(o.from,e.allowFrom)?(i(dispatchVoiceTranscription({config:e,onVoiceTranscription:s,send:n,transcription:o})),sayTwilioResponse(e.voice?.acknowledgement??`Thanks. I'll follow up by text.`)):new Response(`forbidden`,{status:403}):gatherSpeechTwilioResponse({actionUrl:await buildActionUrl(t,e,r.transcription),language:e.voice?.language,prompt:e.voice?.prompt??`Please say your message after the tone.`,speechTimeout:e.voice?.speechTimeout??`auto`,timeoutSeconds:e.voice?.timeoutSeconds})})],async receive(t,{send:n}){let r=readString(t.target.phoneNumber);if(!r)throw Error(`twilioChannel().receive requires target.phoneNumber.`);let i=readString(t.target.from)??e.messaging?.from??null;return n(t.message,{auth:t.auth,continuationToken:twilioContinuationToken(r,i??void 0),state:{from:r,lastCallSid:null,lastMessageSid:null,to:i}})},events:l})}function rebuildTwilioContext(e,t,n){return{state:e,twilio:buildTwilioHandle({callSid:e.lastCallSid??void 0,config:n,from:e.from??``,to:e.to??void 0})}}function buildTwilioHandle(e){let t=e.config.api,n=e.config.credentials,r=e.config.messaging?.from??e.to,o=e.config.messaging?.messagingServiceSid,c=e.config.messaging?.statusCallbackUrl;return{callSid:e.callSid,from:e.from,to:e.to,request(e,r){return callTwilioApi({apiBaseUrl:t?.apiBaseUrl,body:r,credentials:n,fetch:t?.fetch,path:e})},sendMessage(i,s){return sendTwilioMessage({apiBaseUrl:t?.apiBaseUrl,body:i,credentials:n,fetch:t?.fetch,from:s?.from??r,messagingServiceSid:s?.messagingServiceSid??o,statusCallbackUrl:s?.statusCallbackUrl??c,to:s?.to??e.from})},updateCall(e,r){return updateTwilioCall({apiBaseUrl:t?.apiBaseUrl,callSid:e,credentials:n,fetch:t?.fetch,twiml:r})}}}function buildRoutes(e){let t=e.endsWith(`/`)?e.slice(0,-1):e;return{messages:`${t}/messages`,transcription:`${t}/voice/transcription`,voice:`${t}/voice`}}function assertAllowFromConfigured(e){if(e?.allowFrom===void 0)throw Error(`twilioChannel requires allowFrom. Use allowFrom: "*" to allow all numbers.`)}async function verifyInbound(e,t){try{return await verifyTwilioRequest(e,{authToken:t.credentials?.authToken,webhookUrl:t.webhookUrl})}catch(e){return log.warn(`twilio inbound verification failed`,{error:e}),null}}async function dispatchText(e){let{message:t}=e,n={twilio:buildTwilioHandle({callSid:void 0,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onText(n,t)}catch(e){log.error(`text handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({channel:`text`,from:t.from,messageSid:t.messageSid,to:t.to});try{await e.send({message:t.body,context:[i]},{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:null,lastMessageSid:t.messageSid??null,to:t.to??null}})}catch(e){log.error(`text delivery failed`,{error:e})}}async function acceptVoiceCall(e){let{call:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})};try{return await e.onVoice(n,t)}catch(e){return log.error(`voice handler failed`,{error:e}),null}}async function dispatchVoiceTranscription(e){let{transcription:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onVoiceTranscription(n,t)}catch(e){log.error(`voice transcription handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({callSid:t.callSid,channel:`voice`,from:t.from,to:t.to});try{await e.send({message:t.text,context:[i]},{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:t.callSid??null,lastMessageSid:null,to:t.to??null}})}catch(e){log.error(`voice transcription delivery failed`,{error:e})}}async function isAllowed(e,t){let n=typeof t==`function`?await t():t;return n===`*`?!0:typeof n==`string`?n===e:n.includes(e)}async function buildActionUrl(e,t,n){let r=typeof t.publicBaseUrl==`function`?await t.publicBaseUrl(e):t.publicBaseUrl;if(r)return new URL(n,ensureTrailingSlash(r)).toString();let i=new URL(e.url);return i.pathname=n,i.search=``,i.toString()}function ensureTrailingSlash(e){return e.endsWith(`/`)?e:`${e}/`}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{twilioChannel};
@@ -1,6 +1,6 @@
1
1
  import type { FetchFileResult } from "#channel/adapter.js";
2
2
  import { CHANNEL_SENTINEL } from "#channel/compiled-channel.js";
3
- import type { TypedReceiveRoute } from "#channel/receive-args.js";
3
+ import type { TypedReceiveTarget } from "#channel/receive-target.js";
4
4
  import type { SessionAuthContext } from "#channel/types.js";
5
5
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
6
6
  import type { SessionContext } from "#public/definitions/callback-context.js";
@@ -49,12 +49,12 @@ export interface ChannelEvents<TCtx = void> {
49
49
  * Input passed to a channel's `receive` callback when another channel or
50
50
  * schedule proactively routes a message to it.
51
51
  */
52
- export interface ReceiveInput<TReceiveArgs = Record<string, unknown>> {
52
+ export interface ReceiveInput<TReceiveTarget = Record<string, unknown>> {
53
53
  readonly message: string;
54
- readonly args: Readonly<TReceiveArgs>;
54
+ readonly target: Readonly<TReceiveTarget>;
55
55
  readonly auth: SessionAuthContext | null;
56
56
  }
57
- export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> {
57
+ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveTarget = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> {
58
58
  readonly state?: TState;
59
59
  /**
60
60
  * Builds the per-step channel context handed to `events` and
@@ -71,7 +71,7 @@ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs
71
71
  */
72
72
  context?(state: NonNullable<TState>, session: SessionHandle): TCtx;
73
73
  readonly routes: readonly RouteDefinition<TState>[];
74
- receive?(input: ReceiveInput<TReceiveArgs>, args: {
74
+ receive?(input: ReceiveInput<TReceiveTarget>, args: {
75
75
  send: SendFn<TState>;
76
76
  }): Promise<Session>;
77
77
  readonly events?: ChannelEvents<TCtx>;
@@ -108,16 +108,16 @@ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs
108
108
  */
109
109
  readonly kindHint?: string;
110
110
  }
111
- export interface Channel<TState = undefined, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> extends TypedReceiveRoute<TReceiveArgs> {
111
+ export interface Channel<TState = undefined, TReceiveTarget = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> extends TypedReceiveTarget<TReceiveTarget> {
112
112
  readonly __kind: typeof CHANNEL_SENTINEL;
113
113
  readonly [CHANNEL_METADATA_TYPE]?: TMetadata;
114
114
  readonly routes: readonly {
115
115
  method: string;
116
116
  path: string;
117
117
  }[];
118
- readonly receive?: (input: ReceiveInput<TReceiveArgs>, args: {
118
+ readonly receive?: (input: ReceiveInput<TReceiveTarget>, args: {
119
119
  send: SendFn<TState>;
120
120
  }) => Promise<Session>;
121
121
  }
122
122
  export type InferChannelMetadata<TChannel> = TChannel extends Channel<any, any, infer TMetadata> ? TMetadata : Record<string, unknown>;
123
- export declare function defineChannel<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>>(definition: ChannelDefinition<TState, TCtx, TReceiveArgs, TMetadata>): Channel<TState, TReceiveArgs, TMetadata>;
123
+ export declare function defineChannel<TState = undefined, TCtx = void, TReceiveTarget = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>>(definition: ChannelDefinition<TState, TCtx, TReceiveTarget, TMetadata>): Channel<TState, TReceiveTarget, TMetadata>;
@@ -1,7 +1,7 @@
1
1
  import type { CrossChannelReceiveFn } from "#channel/cross-channel-receive.js";
2
2
  import type { SessionAuthContext } from "#channel/types.js";
3
3
  import type { ExactDefinition } from "#public/definitions/exact.js";
4
- export type { InferReceiveArgs, TypedReceiveRoute } from "#channel/receive-args.js";
4
+ export type { InferReceiveTarget, TypedReceiveTarget } from "#channel/receive-target.js";
5
5
  /**
6
6
  * Arguments handed to {@link ScheduleDefinition.run}. Tight subset of
7
7
  * `RouteHandlerArgs` — schedules can hand work off to a channel via
@@ -72,7 +72,7 @@ export type ScheduleDefinition = {
72
72
  * async run({ receive, waitUntil, appAuth }) {
73
73
  * waitUntil(receive(slack, {
74
74
  * message: "Post the daily standup summary.",
75
- * args: { channelId: "C0123ABC" },
75
+ * target: { channelId: "C0123ABC" },
76
76
  * auth: appAuth,
77
77
  * }));
78
78
  * },
@@ -5,6 +5,7 @@ import type { ExactDefinition } from "#public/definitions/exact.js";
5
5
  import type { ModelMessage, SystemModelMessage } from "ai";
6
6
  import type { SessionAuthContext, SessionParent } from "#channel/types.js";
7
7
  import type { Channel } from "#public/definitions/defineChannel.js";
8
+ import type { JsonObject } from "#shared/json.js";
8
9
  /**
9
10
  * Context passed to the {@link InstrumentationDefinition.setup} callback.
10
11
  */
@@ -18,12 +19,12 @@ export interface InstrumentationSetupContext {
18
19
  readonly agentName: string;
19
20
  }
20
21
  /**
21
- * User-authored metadata attached to AI SDK telemetry spans.
22
+ * User-authored runtime context values attached to AI SDK telemetry spans.
22
23
  *
23
- * Keys beginning with `ash.` are reserved for framework-owned metadata
24
+ * Keys beginning with `ash.` are reserved for framework-owned context
24
25
  * and are ignored when returned from authored instrumentation.
25
26
  */
26
- export type InstrumentationMetadata = Readonly<Record<string, string>>;
27
+ export type InstrumentationRuntimeContext = JsonObject;
27
28
  /**
28
29
  * Base channel metadata shape used by framework channel kinds.
29
30
  */
@@ -91,12 +92,12 @@ export interface InstrumentationModelInput {
91
92
  readonly messages: readonly ModelMessage[];
92
93
  }
93
94
  /**
94
- * Input passed to `metadata["step.started"]`.
95
+ * Input passed to `events["step.started"]`.
95
96
  *
96
97
  * The callback runs after Ash has built the final model input for this
97
98
  * model-call attempt and before the AI SDK model call is constructed.
98
99
  */
99
- export interface InstrumentationStepStartedMetadataInput {
100
+ export interface InstrumentationStepStartedEventInput {
100
101
  readonly channel: InstrumentationChannel;
101
102
  readonly modelInput: InstrumentationModelInput;
102
103
  readonly session: InstrumentationSession;
@@ -104,14 +105,23 @@ export interface InstrumentationStepStartedMetadataInput {
104
105
  readonly turn: InstrumentationTurn;
105
106
  }
106
107
  /**
107
- * Metadata hooks accepted by {@link defineInstrumentation}.
108
+ * Result returned by `events["step.started"]`.
108
109
  */
109
- export interface InstrumentationMetadataConfig {
110
+ export interface InstrumentationStepStartedEventResult {
110
111
  /**
111
- * Per-attempt metadata resolved before the model call so child spans
112
+ * Additional runtime context merged into AI SDK telemetry spans.
113
+ */
114
+ readonly runtimeContext: InstrumentationRuntimeContext;
115
+ }
116
+ /**
117
+ * Event hooks accepted by {@link defineInstrumentation}.
118
+ */
119
+ export interface InstrumentationEvents {
120
+ /**
121
+ * Per-attempt runtime context resolved before the model call so child spans
112
122
  * created by the AI SDK inherit the returned values.
113
123
  */
114
- readonly "step.started"?: (input: InstrumentationStepStartedMetadataInput) => InstrumentationMetadata;
124
+ readonly "step.started"?: (input: InstrumentationStepStartedEventInput) => InstrumentationStepStartedEventResult | undefined;
115
125
  }
116
126
  /**
117
127
  * Authored instrumentation settings accepted by `defineInstrumentation`.
@@ -127,9 +137,9 @@ export interface InstrumentationDefinition {
127
137
  */
128
138
  readonly functionId?: string;
129
139
  /**
130
- * Additional metadata merged into AI SDK telemetry spans.
140
+ * Instrumentation event hooks.
131
141
  */
132
- readonly metadata?: InstrumentationMetadataConfig;
142
+ readonly events?: InstrumentationEvents;
133
143
  /**
134
144
  * Whether to record full model inputs in telemetry spans.
135
145
  *
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Schedule authoring helpers for `agent/schedules/*` files.
3
3
  */
4
- export { defineSchedule, type ScheduleDefinition, type ScheduleHandlerArgs, type ScheduleRunHandler, type TypedReceiveRoute, } from "#public/definitions/schedule.js";
4
+ export { defineSchedule, type ScheduleDefinition, type ScheduleHandlerArgs, type ScheduleRunHandler, type TypedReceiveTarget, } from "#public/definitions/schedule.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "experimental-ash",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "bin": {
5
5
  "ash": "./bin/ash.js",
6
6
  "experimental-ash": "./bin/ash.js"