experimental-ash 0.50.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/docs/public/advanced/instrumentation.md +87 -119
- package/dist/docs/public/advanced/typescript-api.md +4 -2
- package/dist/docs/public/channels/discord.mdx +2 -2
- package/dist/docs/public/channels/index.md +47 -6
- package/dist/docs/public/channels/teams.mdx +1 -1
- package/dist/docs/public/channels/telegram.mdx +2 -2
- package/dist/docs/public/meta.json +1 -0
- package/dist/docs/public/onboarding.md +119 -0
- package/dist/docs/public/schedules.mdx +4 -4
- package/dist/docs/public/tools.mdx +5 -40
- package/dist/src/channel/compiled-channel.d.ts +2 -2
- package/dist/src/channel/cross-channel-receive.d.ts +6 -6
- package/dist/src/channel/cross-channel-receive.js +1 -1
- package/dist/src/channel/receive-target.d.ts +17 -0
- package/dist/src/cli/commands/channels.d.ts +2 -0
- package/dist/src/cli/commands/channels.js +1 -1
- package/dist/src/cli/commands/info.d.ts +46 -1
- package/dist/src/cli/commands/info.js +2 -2
- package/dist/src/cli/run.d.ts +3 -1
- package/dist/src/cli/run.js +2 -2
- package/dist/src/execution/node-step.js +1 -1
- package/dist/src/harness/{instrumentation-metadata.d.ts → instrumentation-runtime-context.d.ts} +2 -2
- package/dist/src/harness/instrumentation-runtime-context.js +1 -0
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/harness/tool-result-pruning.d.ts +2 -16
- package/dist/src/harness/tool-result-pruning.js +1 -1
- package/dist/src/harness/types.d.ts +0 -9
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/schema-backed.js +1 -1
- package/dist/src/internal/instrumentation.d.ts +8 -7
- package/dist/src/internal/instrumentation.js +1 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +2 -2
- package/dist/src/packages/ash-scaffold/src/human-action.js +1 -0
- package/dist/src/packages/ash-scaffold/src/index.js +1 -1
- package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +1 -1
- package/dist/src/packages/ash-scaffold/src/steps/setup-slackbot.js +1 -1
- package/dist/src/public/channels/ash.d.ts +59 -1
- package/dist/src/public/channels/ash.js +2 -2
- package/dist/src/public/channels/discord/discordChannel.d.ts +3 -3
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/discord/index.d.ts +1 -1
- package/dist/src/public/channels/slack/index.d.ts +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +4 -3
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/teams/index.d.ts +1 -1
- package/dist/src/public/channels/teams/teamsChannel.d.ts +3 -3
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/telegram/index.d.ts +1 -1
- package/dist/src/public/channels/telegram/telegramChannel.d.ts +3 -3
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/channels/twilio/index.d.ts +1 -1
- package/dist/src/public/channels/twilio/twilioChannel.d.ts +3 -3
- package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
- package/dist/src/public/definitions/defineChannel.d.ts +8 -8
- package/dist/src/public/definitions/schedule.d.ts +2 -2
- package/dist/src/public/definitions/tool.d.ts +0 -40
- package/dist/src/public/instrumentation/index.d.ts +21 -11
- package/dist/src/public/schedules/index.d.ts +1 -1
- package/dist/src/public/tools/index.d.ts +1 -1
- package/dist/src/runtime/framework-tools/skill.js +1 -1
- package/dist/src/runtime/resolve-tool.d.ts +1 -1
- package/dist/src/runtime/resolve-tool.js +1 -1
- package/dist/src/runtime/types.d.ts +1 -9
- package/package.json +1 -1
- package/dist/src/channel/receive-args.d.ts +0 -17
- package/dist/src/harness/instrumentation-metadata.js +0 -1
- /package/dist/src/channel/{receive-args.js → receive-target.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{formatError}from"#internal/logging.js";import{isPlainRecord,isThenable}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";const FRAMEWORK_CHANNEL_KINDS=new Set([`http`,`schedule`,`subagent`]);function isInstrumentationChannelKind(e){return e.startsWith(`channel:`)||FRAMEWORK_CHANNEL_KINDS.has(e)}function normalizeInstrumentationChannelKind(e){return e!==void 0&&isInstrumentationChannelKind(e)?e:`unknown`}function resolveInstrumentationProjection(n){let{invoke:r,log:i,source:a}=n,o;try{o=r()}catch(t){i.warn(`ignoring instrumentation
|
|
1
|
+
import{formatError}from"#internal/logging.js";import{isPlainRecord,isThenable}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";const FRAMEWORK_CHANNEL_KINDS=new Set([`http`,`schedule`,`subagent`]);function isInstrumentationChannelKind(e){return e.startsWith(`channel:`)||FRAMEWORK_CHANNEL_KINDS.has(e)}function normalizeInstrumentationChannelKind(e){return e!==void 0&&isInstrumentationChannelKind(e)?e:`unknown`}function resolveInstrumentationProjection(n){let{invoke:r,log:i,source:a}=n,o;try{o=r()}catch(t){i.warn(`ignoring instrumentation projection after projector failure`,{error:formatError(t),source:a});return}if(isThenable(o)){i.warn(`ignoring instrumentation projection because it returned a Promise`,{source:a}),Promise.resolve(o).catch(t=>{i.warn(`ignored instrumentation projection Promise rejected`,{error:formatError(t),source:a})});return}if(o!==void 0){if(!isPlainRecord(o)){i.warn(`ignoring instrumentation projection because it is not a record`,{source:a});return}try{return parseJsonObject(o)}catch(t){i.warn(`ignoring instrumentation projection because it is outside the JSON contract`,{error:formatError(t),source:a});return}}}export{isInstrumentationChannelKind,normalizeInstrumentationChannelKind,resolveInstrumentationProjection};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.
|
|
1
|
+
import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.52.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.159`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
|
|
2
2
|
import { slackChannel } from "experimental-ash/channels/slack";
|
|
3
3
|
|
|
4
4
|
export default slackChannel({
|
|
5
5
|
credentials: connectSlackCredentials("slack/${e}"),
|
|
6
6
|
});
|
|
7
|
-
`}function renderWebAppTemplate(e,t){return e.replaceAll(`__ASH_INIT_APP_NAME__`,t)}function withWebVercelServices(e){let t=JSON.parse(e);if(!isJsonObject(t))throw Error(`${WEB_VERCEL_JSON_PATH} must contain a JSON object.`);let n=t.experimentalServices;if(n!==void 0&&!isJsonObject(n))throw Error(`${WEB_VERCEL_JSON_PATH} experimentalServices must contain a JSON object.`);let r={...t,$schema:typeof t.$schema==`string`?t.$schema:WEB_VERCEL_JSON_SCHEMA,experimentalServices:{...n,web:n?.web??WEB_DEFAULT_VERCEL_SERVICES.web,ash:n?.ash??WEB_DEFAULT_VERCEL_SERVICES.ash}};return JSON.stringify(t)===JSON.stringify(r)?e:`${JSON.stringify(r,null,2)}\n`}async function ensureWebVercelServices(e){if(!await pathExists(e))return await writeTextFile(e,`${JSON.stringify({$schema:WEB_VERCEL_JSON_SCHEMA,experimentalServices:WEB_DEFAULT_VERCEL_SERVICES},null,2)}\n`,{force:!0}),`written`;let t=await readFile(e,`utf8`),i=withWebVercelServices(t);return i===t?`skipped`:(await writeFile(e,i,`utf8`),`written`)}function assertStampedVersion(e,t){if(t.startsWith(`__`))throw Error(`Channel scaffold received unstamped version token (${e}=${t}). Build @vercel/ash-scaffold before using its dist entrypoint.`)}async function ensureChannel(e){switch(e.kind){case`slack`:return ensureSlackChannel({...e,kind:`slack`});case`web`:return ensureWebChannel({...e,kind:`web`})}}async function ensureWebChannel(e){let t=join(e.projectRoot,`package.json`),s=await pathExists(join(e.projectRoot,`app/page.tsx`));if(!e.force&&await hasPackageDependency(t,`next`))return{kind:`web`,action:`skipped`,skipReason:`nextjs-project`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let c=await patchWebPackageJson(t,resolveWebPackageVersions(e.webPackageVersions)),l=[],u=[],d=basename(resolve(e.projectRoot)),f=join(e.projectRoot,WEB_VERCEL_JSON_PATH);await ensureWebVercelServices(f)===`written`?l.push(f):u.push(f);let p=join(e.projectRoot,PNPM_WORKSPACE_PATH);await ensurePnpmWorkspacePolicy(p)===`written`?l.push(p):u.push(p);for(let[t,i]of Object.entries(WEB_APP_TEMPLATE_FILES)){let a=join(e.projectRoot,t);if(t===`agent/channels/ash.ts`&&!e.force&&await pathExists(a)){u.push(a);continue}await writeTextFile(a,renderWebAppTemplate(i,d),{force:!0}),l.push(a)}return{kind:`web`,action:s?`overwritten`:`created`,filesWritten:l,filesSkipped:u,packageJsonUpdated:c}}async function ensureSlackChannel(e){let t=join(e.projectRoot,`agent/channels/slack.ts`),i=await pathExists(t);if(!e.force&&i)return{kind:`slack`,action:`skipped`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let a=e.connectPackageVersion??`0.
|
|
7
|
+
`}function renderWebAppTemplate(e,t){return e.replaceAll(`__ASH_INIT_APP_NAME__`,t)}function withWebVercelServices(e){let t=JSON.parse(e);if(!isJsonObject(t))throw Error(`${WEB_VERCEL_JSON_PATH} must contain a JSON object.`);let n=t.experimentalServices;if(n!==void 0&&!isJsonObject(n))throw Error(`${WEB_VERCEL_JSON_PATH} experimentalServices must contain a JSON object.`);let r={...t,$schema:typeof t.$schema==`string`?t.$schema:WEB_VERCEL_JSON_SCHEMA,experimentalServices:{...n,web:n?.web??WEB_DEFAULT_VERCEL_SERVICES.web,ash:n?.ash??WEB_DEFAULT_VERCEL_SERVICES.ash}};return JSON.stringify(t)===JSON.stringify(r)?e:`${JSON.stringify(r,null,2)}\n`}async function ensureWebVercelServices(e){if(!await pathExists(e))return await writeTextFile(e,`${JSON.stringify({$schema:WEB_VERCEL_JSON_SCHEMA,experimentalServices:WEB_DEFAULT_VERCEL_SERVICES},null,2)}\n`,{force:!0}),`written`;let t=await readFile(e,`utf8`),i=withWebVercelServices(t);return i===t?`skipped`:(await writeFile(e,i,`utf8`),`written`)}function assertStampedVersion(e,t){if(t.startsWith(`__`))throw Error(`Channel scaffold received unstamped version token (${e}=${t}). Build @vercel/ash-scaffold before using its dist entrypoint.`)}async function ensureChannel(e){switch(e.kind){case`slack`:return ensureSlackChannel({...e,kind:`slack`});case`web`:return ensureWebChannel({...e,kind:`web`})}}async function ensureWebChannel(e){let t=join(e.projectRoot,`package.json`),s=await pathExists(join(e.projectRoot,`app/page.tsx`));if(!e.force&&await hasPackageDependency(t,`next`))return{kind:`web`,action:`skipped`,skipReason:`nextjs-project`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let c=await patchWebPackageJson(t,resolveWebPackageVersions(e.webPackageVersions)),l=[],u=[],d=basename(resolve(e.projectRoot)),f=join(e.projectRoot,WEB_VERCEL_JSON_PATH);await ensureWebVercelServices(f)===`written`?l.push(f):u.push(f);let p=join(e.projectRoot,PNPM_WORKSPACE_PATH);await ensurePnpmWorkspacePolicy(p)===`written`?l.push(p):u.push(p);for(let[t,i]of Object.entries(WEB_APP_TEMPLATE_FILES)){let a=join(e.projectRoot,t);if(t===`agent/channels/ash.ts`&&!e.force&&await pathExists(a)){u.push(a);continue}await writeTextFile(a,renderWebAppTemplate(i,d),{force:!0}),l.push(a)}return{kind:`web`,action:s?`overwritten`:`created`,filesWritten:l,filesSkipped:u,packageJsonUpdated:c}}async function ensureSlackChannel(e){let t=join(e.projectRoot,`agent/channels/slack.ts`),i=await pathExists(t);if(!e.force&&i)return{kind:`slack`,action:`skipped`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let a=e.connectPackageVersion??`0.1.1`;assertStampedVersion(`connectPackageVersion`,a);let o=await ensurePackageDependency(join(e.projectRoot,`package.json`),`@vercel/connect`,a),s=e.slackConnectorSlug??await deriveSlackConnectorSlug(e.projectRoot);return await writeTextFile(t,buildSlackTemplate(s),{force:e.force}),{kind:`slack`,action:i?`overwritten`:`created`,filesWritten:[t],filesSkipped:[],packageJsonUpdated:o,slackConnectorSlug:s}}async function listAuthoredChannels(n){let r=join(n,`agent/channels`),i;try{i=await readdir(r,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}let a=[];for(let n of i){if(n.isFile()){let t=getSupportedModuleBaseName(n.name);t!==null&&a.push(t);continue}if(n.isDirectory())try{(await readdir(join(r,n.name))).some(e=>matchesSupportedModuleBaseName(e,`connection`))&&a.push(n.name)}catch{}}return a.sort()}export{DEFAULT_SLACK_CONNECTOR_SLUG,SLACK_CHANNEL_DEFAULT_ROUTE,deriveSlackConnectorSlug,ensureChannel,listAuthoredChannels,normalizeSlackConnectorSlug};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var HumanActionRequiredError=class extends Error{action;constructor(e){super(`Human action required: \`${e.command}\` — ${e.reason}`),this.name=`HumanActionRequiredError`,this.action=e}};export{HumanActionRequiredError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./files.js";import"./project.js";import"./channels.js";export{};
|
|
1
|
+
import"./files.js";import"./project.js";import"./channels.js";import"./human-action.js";export{};
|
|
@@ -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,
|
|
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
|
|
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};
|
|
@@ -1,6 +1,51 @@
|
|
|
1
|
+
import { type UserContent } from "ai";
|
|
2
|
+
import type { SessionAuthContext } from "#channel/types.js";
|
|
1
3
|
import { type AuthFn } from "#public/channels/auth.js";
|
|
2
4
|
import { type UploadPolicyInput } from "#public/channels/upload-policy.js";
|
|
3
|
-
import { type Channel } from "#public/definitions/defineChannel.js";
|
|
5
|
+
import { type Channel, type ChannelEvents, type ChannelSessionOps } from "#public/definitions/defineChannel.js";
|
|
6
|
+
/**
|
|
7
|
+
* Event-handler channel context exposed by `ashChannel({ events })`.
|
|
8
|
+
*
|
|
9
|
+
* The default Ash HTTP channel has no platform-specific state, so handlers
|
|
10
|
+
* receive session continuation operations plus the separate `SessionContext`
|
|
11
|
+
* third argument supplied by {@link ChannelEvents}.
|
|
12
|
+
*/
|
|
13
|
+
export type AshEventContext = ChannelSessionOps;
|
|
14
|
+
/**
|
|
15
|
+
* Runtime stream-event handlers supported by `ashChannel({ events })`.
|
|
16
|
+
*/
|
|
17
|
+
export type AshChannelEvents = ChannelEvents<AshEventContext>;
|
|
18
|
+
/** Low-level Ash HTTP handle exposed to `ashChannel({ onMessage })`. */
|
|
19
|
+
export interface AshHandle {
|
|
20
|
+
/**
|
|
21
|
+
* Route-auth result for the inbound HTTP request. `onMessage`
|
|
22
|
+
* chooses runtime session auth by returning `{ auth }`.
|
|
23
|
+
*/
|
|
24
|
+
readonly caller: SessionAuthContext | null;
|
|
25
|
+
readonly request: Request;
|
|
26
|
+
/** Existing runtime session id for continuation requests. */
|
|
27
|
+
readonly sessionId?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Pre-dispatch context passed to `ashChannel({ onMessage })`. */
|
|
30
|
+
export interface AshMessageContext {
|
|
31
|
+
readonly ash: AshHandle;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Result of `ashChannel({ onMessage })`. Return an object to dispatch
|
|
35
|
+
* the inbound message, optionally appending `context` strings as user
|
|
36
|
+
* messages before the message. Return `null` to accept the request
|
|
37
|
+
* without dispatching a turn.
|
|
38
|
+
*/
|
|
39
|
+
export type AshMessageResult = {
|
|
40
|
+
readonly auth: SessionAuthContext | null;
|
|
41
|
+
readonly context?: readonly string[];
|
|
42
|
+
} | null;
|
|
43
|
+
/**
|
|
44
|
+
* Synchronous or asynchronous `onMessage` result.
|
|
45
|
+
*/
|
|
46
|
+
export type AshMessageResultOrPromise = AshMessageResult | Promise<AshMessageResult>;
|
|
47
|
+
/** Default runtime auth projection for the Ash HTTP channel. */
|
|
48
|
+
export declare function defaultAshAuth(ctx: AshMessageContext): SessionAuthContext | null;
|
|
4
49
|
export interface AshChannelInput {
|
|
5
50
|
/**
|
|
6
51
|
* Route auth policy. Accepts a single {@link AuthFn} or an ordered
|
|
@@ -12,6 +57,19 @@ export interface AshChannelInput {
|
|
|
12
57
|
*/
|
|
13
58
|
readonly auth: AuthFn<Request> | readonly AuthFn<Request>[];
|
|
14
59
|
readonly uploadPolicy?: UploadPolicyInput;
|
|
60
|
+
/**
|
|
61
|
+
* Pre-dispatch hook for inbound Ash HTTP messages. Runs after route
|
|
62
|
+
* auth and body parsing, and before the runtime dispatch.
|
|
63
|
+
*/
|
|
64
|
+
readonly onMessage?: (ctx: AshMessageContext, message: string | UserContent) => AshMessageResultOrPromise;
|
|
65
|
+
/**
|
|
66
|
+
* Runtime stream-event handlers for the default Ash HTTP channel.
|
|
67
|
+
*
|
|
68
|
+
* Handlers receive the event data, {@link AshEventContext}, and
|
|
69
|
+
* `SessionContext` so they can observe events with the same session
|
|
70
|
+
* context shape as custom channel implementations.
|
|
71
|
+
*/
|
|
72
|
+
readonly events?: AshChannelEvents;
|
|
15
73
|
}
|
|
16
74
|
/**
|
|
17
75
|
* Concrete return type of {@link ashChannel}. Named so consumers can
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ASH_MESSAGE_STREAM_CONTENT_TYPE,ASH_MESSAGE_STREAM_FORMAT,ASH_MESSAGE_STREAM_VERSION,ASH_SESSION_ID_HEADER,ASH_STREAM_FORMAT_HEADER,ASH_STREAM_VERSION_HEADER}from"#protocol/message.js";import{isInputResponse}from"#runtime/input/types.js";import{parseJsonObject}from"#shared/json.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{routeAuth}from"#public/channels/auth.js";import{collectUploadPolicyViolations,formatUploadPolicyViolation,mergeUploadPolicy}from"#public/channels/upload-policy.js";import{GET,POST,defineChannel}from"#public/definitions/defineChannel.js";function ashChannel(
|
|
2
|
-
${e}`}function parseStartIndex(e){let t=new URL(e.url).searchParams.get(`startIndex`);if(t===null)return;let n=Number.parseInt(t,10);return!Number.isSafeInteger(n)||n<0?Response.json({error:`Expected startIndex to be a non-negative integer.`,ok:!1},{status:400}):n}function serializeAsNdjson(e){let t=new TextEncoder;return e.pipeThrough(new TransformStream({transform(e,n){n.enqueue(t.encode(`${JSON.stringify(e)}\n`))}}))}export{ashChannel};
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{ASH_MESSAGE_STREAM_CONTENT_TYPE,ASH_MESSAGE_STREAM_FORMAT,ASH_MESSAGE_STREAM_VERSION,ASH_SESSION_ID_HEADER,ASH_STREAM_FORMAT_HEADER,ASH_STREAM_VERSION_HEADER}from"#protocol/message.js";import{isInputResponse}from"#runtime/input/types.js";import{parseJsonObject}from"#shared/json.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{routeAuth}from"#public/channels/auth.js";import{collectUploadPolicyViolations,formatUploadPolicyViolation,mergeUploadPolicy}from"#public/channels/upload-policy.js";import{GET,POST,defineChannel}from"#public/definitions/defineChannel.js";const log=createLogger(`ash.channel`);function defaultAshAuth(e){return e.ash.caller}function ashChannel(e){let t=mergeUploadPolicy(e.uploadPolicy);return defineChannel({routes:[POST(`/ash/v1/session`,async(n,{send:r})=>{let i=await routeAuth(n,e.auth);if(i instanceof Response)return i;let o=i,s;try{s=await n.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof s!=`object`||!s)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let c=parseCreateBody(s);if(c instanceof Response)return c;let l=checkUploadPolicy(c,t);if(l!==null)return l;let u=await resolveOnMessage({auth:o,config:e,message:c.message,request:n});if(u instanceof Response)return u;if(!u.dispatch)return droppedMessageResponse();let d=`ash:${crypto.randomUUID()}`,f=await r(createSendPayload(c,mergeContext(c.context,u.context)),{auth:u.auth,callback:c.callback,continuationToken:d,mode:c.mode});return Response.json({continuationToken:f.continuationToken,ok:!0,sessionId:f.id},{headers:{"cache-control":`no-store`,[ASH_SESSION_ID_HEADER]:f.id},status:202})}),POST(`/ash/v1/session/:sessionId`,async(n,{send:r,getSession:i,params:o})=>{let s=await routeAuth(n,e.auth);if(s instanceof Response)return s;let c=s,l=o.sessionId;if(!l)return Response.json({error:`Missing session id.`,ok:!1},{status:400});try{i(l)}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}let u;try{u=await n.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof u!=`object`||!u)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let d=parseContinueBody(u);if(d instanceof Response)return d;let f=checkUploadPolicy(d,t);if(f!==null)return f;let p=d.context,m=c;if(d.message!==void 0){let t=await resolveOnMessage({auth:c,config:e,message:d.message,request:n,sessionId:l});if(t instanceof Response)return t;if(!t.dispatch)return droppedMessageResponse();p=mergeContext(d.context,t.context),m=t.auth}let h=await r({inputResponses:d.inputResponses,message:d.message,context:p,outputSchema:d.outputSchema},{auth:m,continuationToken:d.continuationToken});return Response.json({ok:!0,sessionId:h.id},{headers:{"cache-control":`no-store`,[ASH_SESSION_ID_HEADER]:h.id},status:200})}),GET(`/ash/v1/session/:sessionId/stream`,async(t,{getSession:c,params:l})=>{let u=await routeAuth(t,e.auth);if(u instanceof Response)return u;let d=l.sessionId;if(!d)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let f=parseStartIndex(t);if(f instanceof Response)return f;try{let e=serializeAsNdjson(await c(d).getEventStream({startIndex:f}));return new Response(e,{headers:{"cache-control":`no-store`,"content-type":ASH_MESSAGE_STREAM_CONTENT_TYPE,[ASH_SESSION_ID_HEADER]:d,[ASH_STREAM_FORMAT_HEADER]:ASH_MESSAGE_STREAM_FORMAT,[ASH_STREAM_VERSION_HEADER]:ASH_MESSAGE_STREAM_VERSION}})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}})],events:e.events})}async function resolveOnMessage(e){let n=e.config.onMessage??defaultOnMessage,r;try{r=await n({ash:e.sessionId===void 0?{caller:e.auth,request:e.request}:{caller:e.auth,request:e.request,sessionId:e.sessionId}},e.message)}catch(n){let r=logError(log,`onMessage handler failed`,n,{sessionId:e.sessionId});return Response.json({error:`onMessage handler failed.`,errorId:r,ok:!1},{status:500})}return r==null?{dispatch:!1}:r.context===void 0?{auth:r.auth,dispatch:!0}:{auth:r.auth,context:r.context,dispatch:!0}}function defaultOnMessage(e){return{auth:defaultAshAuth(e)}}function droppedMessageResponse(){return new Response(null,{headers:{"cache-control":`no-store`},status:204})}function parseCreateBody(e){let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseModeField(e.mode);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:t===void 0?Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400}):{callback:r,message:t,mode:i,context:n,outputSchema:a}}function parseContinueBody(e){let t=typeof e.continuationToken==`string`&&e.continuationToken.length>0?e.continuationToken:void 0;if(t===void 0)return Response.json({error:`Missing or empty 'continuationToken' field.`,ok:!1},{status:400});let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseInputResponses(e.inputResponses);if(r instanceof Response)return r;let i=parseClientContextField(e.clientContext);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:n===void 0&&r===void 0?Response.json({error:`Expected a non-empty 'message', a non-empty 'inputResponses' array, or both.`,ok:!1},{status:400}):{message:n,continuationToken:t,inputResponses:r,context:i,outputSchema:a}}function createSendPayload(e,t=e.context){if(t===void 0&&e.outputSchema===void 0)return e.message;let n={message:e.message};return t!==void 0&&(n.context=t),e.outputSchema!==void 0&&(n.outputSchema=e.outputSchema),n}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});let e={type:`file`,mediaType:t.mediaType,data:t.data};return typeof t.filename==`string`&&t.filename.length>0&&(e.filename=t.filename),e}return Response.json({error:`Unsupported message part type "${String(t.type)}". Use 'text' or 'file'.`,ok:!1},{status:400})}function checkUploadPolicy(e,t){if(!e.message)return null;let n=collectUploadPolicyViolations(e.message,t);if(n.length===0)return null;let[r]=n;if(!r)return null;let i=r.kind===`too-large`?413:415;return Response.json({error:formatUploadPolicyViolation(r),ok:!1,violations:n.map(e=>e.kind===`too-large`?{byteLength:e.byteLength,filename:e.filename,kind:e.kind,limit:e.limit,mediaType:e.mediaType}:{allowedMediaTypes:e.allowedMediaTypes,filename:e.filename,kind:e.kind,mediaType:e.mediaType})},{status:i})}function parseInputResponses(e){if(e===void 0)return;if(!Array.isArray(e)||e.length===0)return Response.json({error:`Expected 'inputResponses' to be a non-empty array.`,ok:!1},{status:400});let t=e.filter(isInputResponse);return t.length===e.length?t:Response.json({error:`Expected every 'inputResponses' entry to match the HITL response schema.`,ok:!1},{status:400})}function mergeContext(e,t){return e===void 0?t:t===void 0?e:[...e,...t]}function parseClientContextField(e){if(e!==void 0){if(typeof e==`string`)return e.length>0?[toClientContextMessage(e)]:void 0;if(Array.isArray(e))return e.length===0?void 0:e.every(e=>typeof e==`string`&&e.length>0)?e.map(e=>toClientContextMessage(e)):Response.json({error:`Expected 'clientContext' array entries to be non-empty strings.`,ok:!1},{status:400});if(typeof e!=`object`||!e)return Response.json({error:`Expected 'clientContext' to be a string, string array, or JSON object.`,ok:!1},{status:400});try{let t=parseJsonObject(e);return[toClientContextMessage(JSON.stringify(t))]}catch{return Response.json({error:`Expected 'clientContext' to be a JSON-serializable object.`,ok:!1},{status:400})}}}function toClientContextMessage(e){return`Client context:
|
|
2
|
+
${e}`}function parseStartIndex(e){let t=new URL(e.url).searchParams.get(`startIndex`);if(t===null)return;let n=Number.parseInt(t,10);return!Number.isSafeInteger(n)||n<0?Response.json({error:`Expected startIndex to be a non-negative integer.`,ok:!1},{status:400}):n}function serializeAsNdjson(e){let t=new TextEncoder;return e.pipeThrough(new TransformStream({transform(e,n){n.enqueue(t.encode(`${JSON.stringify(e)}\n`))}}))}export{ashChannel,defaultAshAuth};
|
|
@@ -49,8 +49,8 @@ export interface DiscordChannelCredentials extends DiscordCredentials {
|
|
|
49
49
|
*/
|
|
50
50
|
readonly webhookVerifier?: DiscordWebhookVerifier;
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
53
|
-
export interface
|
|
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,
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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 `
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
/**
|
|
61
|
-
export interface
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
/**
|
|
55
|
-
export interface
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
/**
|
|
47
|
-
export interface
|
|
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,
|
|
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;
|