experimental-ash 0.48.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/docs/internals/context.md +6 -12
  3. package/dist/docs/internals/hooks.md +15 -74
  4. package/dist/docs/internals/mechanical-invariants.md +3 -4
  5. package/dist/docs/internals/message-runtime.md +2 -3
  6. package/dist/docs/public/advanced/hooks.mdx +39 -76
  7. package/dist/docs/public/advanced/project-layout.md +1 -2
  8. package/dist/src/compiler/manifest.d.ts +1 -1
  9. package/dist/src/compiler/normalize-hook.d.ts +4 -4
  10. package/dist/src/context/hook-lifecycle.d.ts +1 -46
  11. package/dist/src/context/hook-lifecycle.js +1 -1
  12. package/dist/src/context/keys.d.ts +0 -9
  13. package/dist/src/context/keys.js +1 -1
  14. package/dist/src/execution/create-session-step.d.ts +3 -4
  15. package/dist/src/execution/create-session-step.js +1 -1
  16. package/dist/src/execution/dispatch-runtime-actions-step.d.ts +2 -3
  17. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  18. package/dist/src/execution/durable-session-store.d.ts +24 -24
  19. package/dist/src/execution/durable-session-store.js +1 -1
  20. package/dist/src/execution/turn-workflow.d.ts +4 -5
  21. package/dist/src/execution/turn-workflow.js +1 -1
  22. package/dist/src/execution/workflow-entry.js +1 -1
  23. package/dist/src/execution/workflow-runtime.d.ts +1 -1
  24. package/dist/src/execution/workflow-steps.d.ts +1 -3
  25. package/dist/src/execution/workflow-steps.js +1 -1
  26. package/dist/src/harness/step-hooks.js +1 -1
  27. package/dist/src/harness/tool-loop.js +1 -1
  28. package/dist/src/internal/application/package.js +1 -1
  29. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  30. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  31. package/dist/src/public/channels/discord/inbound.d.ts +0 -3
  32. package/dist/src/public/channels/discord/inbound.js +1 -1
  33. package/dist/src/public/channels/discord/index.d.ts +1 -1
  34. package/dist/src/public/channels/discord/index.js +1 -1
  35. package/dist/src/public/channels/slack/inbound.d.ts +4 -13
  36. package/dist/src/public/channels/slack/inbound.js +1 -1
  37. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  38. package/dist/src/public/channels/teams/inbound.d.ts +0 -3
  39. package/dist/src/public/channels/teams/inbound.js +2 -2
  40. package/dist/src/public/channels/teams/index.d.ts +1 -1
  41. package/dist/src/public/channels/teams/index.js +1 -1
  42. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  43. package/dist/src/public/channels/telegram/inbound.d.ts +0 -3
  44. package/dist/src/public/channels/telegram/inbound.js +1 -1
  45. package/dist/src/public/channels/telegram/index.d.ts +1 -1
  46. package/dist/src/public/channels/telegram/index.js +1 -1
  47. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  48. package/dist/src/public/channels/twilio/inbound.d.ts +0 -3
  49. package/dist/src/public/channels/twilio/inbound.js +1 -1
  50. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  51. package/dist/src/public/definitions/hook.d.ts +6 -22
  52. package/dist/src/public/hooks/index.d.ts +4 -5
  53. package/dist/src/runtime/graph.d.ts +2 -3
  54. package/dist/src/runtime/hooks/registry.d.ts +5 -20
  55. package/dist/src/runtime/hooks/registry.js +1 -1
  56. package/dist/src/runtime/resolve-hook.d.ts +2 -2
  57. package/dist/src/runtime/resolve-hook.js +1 -1
  58. package/dist/src/runtime/types.d.ts +3 -9
  59. package/package.json +1 -1
@@ -8,11 +8,11 @@
8
8
  * a channel-owned {@link SlackMessage}, with the body's text already
9
9
  * re-rendered as GFM markdown so the agent does not see raw
10
10
  * `<@U…>` / `<https://…|…>` fragments.
11
- * 2. {@link prependSlackContext} attaches a `<slack_context>` block
12
- * naming the actor, channel, and thread so the agent's prompt always
13
- * knows who and where it is talking.
11
+ * 2. {@link formatSlackContextBlock} renders a `<slack_context>` block
12
+ * naming the actor, channel, and thread. The channel delivers it as
13
+ * a dedicated context entry so the agent always knows who and where
14
+ * it is talking.
14
15
  */
15
- import type { UserContent } from "ai";
16
16
  /**
17
17
  * Author metadata for an inbound Slack message. Channel-owned shape;
18
18
  * does not depend on the chat SDK's `Author` interface.
@@ -116,12 +116,3 @@ export interface SlackInboundContext {
116
116
  * the block out of its prompt if it wants to.
117
117
  */
118
118
  export declare function formatSlackContextBlock(context: SlackInboundContext): string;
119
- /**
120
- * Prepends a `<slack_context>` block to the inbound turn message.
121
- *
122
- * Accepts either a raw string (no attachments) or a {@link UserContent}
123
- * array (text + file parts). In the array case the context block lands
124
- * as the first {@link TextPart} — followed by the original parts — so
125
- * attachments stay intact.
126
- */
127
- export declare function prependSlackContext(message: string | UserContent, context: SlackInboundContext): string | UserContent;
@@ -1,2 +1,2 @@
1
1
  import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";function parseAppMentionEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`app_mention`?null:buildSlackMessage(t,e.team_id)}function parseDirectMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;if(!t||t.type!==`message`)return null;let n=t;return n.channel_type!==`im`||typeof n.subtype==`string`&&n.subtype.length>0||typeof n.bot_id==`string`&&n.bot_id.length>0?null:buildSlackMessage(n,e.team_id)}function buildSlackMessage(t,n){let r=typeof t.channel==`string`?t.channel:``,i=typeof t.ts==`string`?t.ts:``;if(!r||!i)return null;let a=typeof t.text==`string`?t.text:``,o=typeof t.thread_ts==`string`?t.thread_ts:i,s=typeof n==`string`?n:void 0;return{text:a,markdown:slackMrkdwnToGfm(a),ts:i,threadTs:o,channelId:r,teamId:s,author:parseAuthor(t),attachments:parseAttachments(t.files),raw:t}}function parseAuthor(e){let t=typeof e.user==`string`?e.user:``;if(t)return{userId:t,userName:typeof e.username==`string`?e.username:void 0,fullName:void 0,isBot:typeof e.bot_id==`string`&&e.bot_id.length>0,isMe:!1}}function parseAttachments(e){return Array.isArray(e)?e.map(toAttachment):[]}function toAttachment(e){let t=typeof e.mimetype==`string`?e.mimetype:void 0,n=typeof e.url_private==`string`?e.url_private:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:n,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}function inferAttachmentType(e){return e===void 0?`file`:e.startsWith(`image/`)?`image`:e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:`file`}function formatSlackContextBlock(e){return[`<slack_context>`,`user_id: ${e.userId}`,...e.userName?[`user_name: ${e.userName}`]:[],...e.fullName?[`full_name: ${e.fullName}`]:[],`channel_id: ${e.channelId}`,`thread_ts: ${e.threadTs}`,...e.teamId?[`team_id: ${e.teamId}`]:[],`</slack_context>`].join(`
2
- `)}function prependSlackContext(e,t){let n=formatSlackContextBlock(t);return typeof e==`string`?e.length>0?`${n}\n\n${e}`:n:[{type:`text`,text:n},...e]}export{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent,prependSlackContext};
2
+ `)}export{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent};
@@ -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{parseAppMentionEvent,parseDirectMessageEvent,prependSlackContext}from"#public/channels/slack/inbound.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),l=createSlackFetchFile({botToken:e.credentials?.botToken}),u=e.onAppMention??defaultOnAppMention,d=e.onDirectMessage??defaultOnDirectMessage,f={...defaultEvents,...e.events,"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler()};return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,pendingAuthMessageTs:{}},fetchFile:l,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{send:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials);return a===null?new Response(`unauthorized`,{status:401}):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(a,{send:r,waitUntil:i},{config:e}):handleEventPost({body:a,send:r,waitUntil:i,onAppMention:u,onDirectMessage:d,uploadPolicy:t,credentials:e.credentials})})],async receive(t,{send:n}){let r=t.args,i=r.channelId;if(!i||typeof i!=`string`)throw Error(`slackChannel().receive requires args.channelId.`);let o=typeof r.threadTs==`string`?r.threadTs:``,s=r.initialMessage;if(s&&o.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=o;if(s){let{thread:t}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:i,threadTs:``,teamId:void 0}),n={card:s.card};s.fallbackText!==void 0&&(n.fallbackText=s.fallbackText),c=(await t.post(n)).id}return n(t.message,{auth:t.auth,continuationToken:slackContinuationToken(i,c),state:{channelId:i,threadTs:c||null,teamId:null,triggeringUserId:null}})},events:f})}async function handleEventPost(e){let t;try{t=JSON.parse(e.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(typeof t.challenge==`string`)return new Response(t.challenge,{status:200,headers:{"content-type":`text/plain`}});let n=parseAppMentionEvent(t);if(n)return e.waitUntil(dispatchInboundMessage({kind:`app_mention`,message:n,handler:e.onAppMention,send:e.send,uploadPolicy:e.uploadPolicy,credentials:e.credentials})),new Response(`ok`);let r=parseDirectMessageEvent(t);return r&&e.waitUntil(dispatchInboundMessage({kind:`direct_message`,message:r,handler:e.onDirectMessage,send:e.send,uploadPolicy:e.uploadPolicy,credentials:e.credentials})),new Response(`ok`)}async function verifyInbound(e,t){try{return await verifySlackRequest(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function dispatchInboundMessage(e){let{message:n,kind:r}=e,{thread:i,slack:o}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:n.channelId,threadTs:n.threadTs,teamId:n.teamId}),s={thread:i,slack:o},c;try{c=await e.handler(s,n)}catch(e){logError(log,`${r} handler failed`,e,{channelId:n.channelId});return}if(c!=null)try{let t=await collectInboundFileParts({mention:n,thread:i,policy:e.uploadPolicy}),r=buildSlackTurnMessage(n.markdown,t),a={channelId:n.channelId,fullName:n.author?.fullName,teamId:n.teamId,threadTs:n.threadTs,userId:n.author?.userId??``,userName:n.author?.userName},o=a.userId?prependSlackContext(r,a):r;await e.send({message:o,context:c.context},{auth:c.auth,continuationToken:slackContinuationToken(n.channelId,n.threadTs),state:{channelId:n.channelId,threadTs:n.threadTs,teamId:n.teamId??null,triggeringUserId:a.userId||null}})}catch(e){logError(log,`${r} delivery failed`,e,{channelId:n.channelId})}}export{slackChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{buildSlackBinding,slackContinuationToken}from"#public/channels/slack/api.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent}from"#public/channels/slack/inbound.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),l=createSlackFetchFile({botToken:e.credentials?.botToken}),u=e.onAppMention??defaultOnAppMention,d=e.onDirectMessage??defaultOnDirectMessage,f={...defaultEvents,...e.events,"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler()};return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,pendingAuthMessageTs:{}},fetchFile:l,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{send:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials);return a===null?new Response(`unauthorized`,{status:401}):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(a,{send:r,waitUntil:i},{config:e}):handleEventPost({body:a,send:r,waitUntil:i,onAppMention:u,onDirectMessage:d,uploadPolicy:t,credentials:e.credentials})})],async receive(t,{send:n}){let r=t.args,i=r.channelId;if(!i||typeof i!=`string`)throw Error(`slackChannel().receive requires args.channelId.`);let o=typeof r.threadTs==`string`?r.threadTs:``,s=r.initialMessage;if(s&&o.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=o;if(s){let{thread:t}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:i,threadTs:``,teamId:void 0}),n={card:s.card};s.fallbackText!==void 0&&(n.fallbackText=s.fallbackText),c=(await t.post(n)).id}return n(t.message,{auth:t.auth,continuationToken:slackContinuationToken(i,c),state:{channelId:i,threadTs:c||null,teamId:null,triggeringUserId:null}})},events:f})}async function handleEventPost(e){let t;try{t=JSON.parse(e.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(typeof t.challenge==`string`)return new Response(t.challenge,{status:200,headers:{"content-type":`text/plain`}});let n=parseAppMentionEvent(t);if(n)return e.waitUntil(dispatchInboundMessage({kind:`app_mention`,message:n,handler:e.onAppMention,send:e.send,uploadPolicy:e.uploadPolicy,credentials:e.credentials})),new Response(`ok`);let r=parseDirectMessageEvent(t);return r&&e.waitUntil(dispatchInboundMessage({kind:`direct_message`,message:r,handler:e.onDirectMessage,send:e.send,uploadPolicy:e.uploadPolicy,credentials:e.credentials})),new Response(`ok`)}async function verifyInbound(e,t){try{return await verifySlackRequest(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function dispatchInboundMessage(e){let{message:n,kind:r}=e,{thread:i,slack:o}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:n.channelId,threadTs:n.threadTs,teamId:n.teamId}),s={thread:i,slack:o},c;try{c=await e.handler(s,n)}catch(e){logError(log,`${r} handler failed`,e,{channelId:n.channelId});return}if(c!=null)try{let t=await collectInboundFileParts({mention:n,thread:i,policy:e.uploadPolicy}),r=buildSlackTurnMessage(n.markdown,t),a={channelId:n.channelId,fullName:n.author?.fullName,teamId:n.teamId,threadTs:n.threadTs,userId:n.author?.userId??``,userName:n.author?.userName},o=c.context??[];await e.send({message:r,context:[formatSlackContextBlock(a),...o]},{auth:c.auth,continuationToken:slackContinuationToken(n.channelId,n.threadTs),state:{channelId:n.channelId,threadTs:n.threadTs,teamId:n.teamId??null,triggeringUserId:a.userId||null}})}catch(e){logError(log,`${r} delivery failed`,e,{channelId:n.channelId})}}export{slackChannel};
@@ -4,7 +4,6 @@
4
4
  * The channel owns small, documented data shapes instead of exposing the
5
5
  * full Bot Framework SDK Activity model as the primary public API.
6
6
  */
7
- import type { UserContent } from "ai";
8
7
  import type { TeamsAttachment, TeamsChannelAccount, TeamsMention } from "#public/channels/teams/api.js";
9
8
  /** Teams conversation scopes handled by the native channel. */
10
9
  export type TeamsConversationScope = "channel" | "groupChat" | "personal" | "unknown";
@@ -76,5 +75,3 @@ export declare function isTeamsPersonalMessage(activity: TeamsMessageActivity):
76
75
  export declare function teamsThreadRootActivityId(activity: TeamsMessageActivity | TeamsInvokeActivity): string | null;
77
76
  /** Renders one {@link TeamsInboundContext} as a deterministic context block. */
78
77
  export declare function formatTeamsContextBlock(context: TeamsInboundContext): string;
79
- /** Prepends a `<teams_context>` block to the inbound turn message. */
80
- export declare function prependTeamsContext(message: string | UserContent, context: TeamsInboundContext): string | UserContent;
@@ -1,3 +1,3 @@
1
1
  import{isNonEmptyString,isObject}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";import TurndownService from"#compiled/turndown/index.js";const HTML_ENTITY_MAP={"&amp;":`&`,"&gt;":`>`,"&lt;":`<`,"&nbsp;":` `,"&quot;":`"`,"&#39;":`'`,"&#x27;":`'`},HTML_ENTITY_PATTERN=new RegExp(Object.keys(HTML_ENTITY_MAP).join(`|`),`gi`);let turndownService=null;function parseTeamsActivity(e){return isObject(e)?e.type===`message`?parseMessageActivity(e):e.type===`invoke`?parseInvokeActivity(e):e.type===`conversationUpdate`?parseConversationUpdateActivity(e):null:null}function isTeamsPersonalMessage(e){return e.scope===`personal`}function teamsThreadRootActivityId(e){return e.scope===`personal`?null:e.replyToId??e.id}function formatTeamsContextBlock(e){return[`<teams_context>`,`response_medium: microsoft_teams`,`response_instructions: Reply for Microsoft Teams in concise Markdown. Avoid broad mentions, large tables, and messages that need more than a few short posts.`,`user_id: ${e.userId}`,...e.userName?[`user_name: ${e.userName}`]:[],`conversation_id: ${e.conversationId}`,`scope: ${e.scope}`,...e.conversationType?[`conversation_type: ${e.conversationType}`]:[],...e.tenantId?[`tenant_id: ${e.tenantId}`]:[],...e.teamId?[`team_id: ${e.teamId}`]:[],...e.channelId?[`channel_id: ${e.channelId}`]:[],`activity_id: ${e.activityId}`,`</teams_context>`].join(`
2
- `)}function prependTeamsContext(e,t){let n=formatTeamsContextBlock(t);return typeof e==`string`?e.length>0?`${n}\n\n${e}`:n:[{type:`text`,text:n},...e]}function parseMessageActivity(n){let r=parseActivityBase(n);if(!r)return null;let i=parseMentions(n.entities),a=normalizeTeamsText(stripBotMention(readText(n),i,r.recipient.id));return{...r,attachments:parseAttachments(n.attachments),isBotMentioned:i.some(e=>e.mentioned.id===r.recipient.id),mentions:i,replyToId:isNonEmptyString(n.replyToId)?n.replyToId:void 0,scope:inferScope(r.conversation),text:a,textFormat:isNonEmptyString(n.textFormat)?n.textFormat:void 0,type:`message`,value:isObject(n.value)?n.value:void 0}}function parseInvokeActivity(n){let r=parseActivityBase(n);return!r||!isNonEmptyString(n.name)?null:{...r,name:n.name,replyToId:isNonEmptyString(n.replyToId)?n.replyToId:void 0,scope:inferScope(r.conversation),type:`invoke`,value:isObject(n.value)?n.value:void 0}}function parseConversationUpdateActivity(e){let t=parseActivityBase(e);return t?{...t,type:`conversationUpdate`}:null}function parseActivityBase(n){if(!isNonEmptyString(n.serviceUrl))return null;let r=parseConversation(n.conversation),i=parseChannelAccount(n.from),a=parseChannelAccount(n.recipient);if(!r||!i||!a)return null;let o=isObject(n.channelData)?n.channelData:{},s=readNestedString(o,[`tenant`,`id`])??r.tenantId,c=readNestedString(o,[`team`,`id`]),l=readNestedString(o,[`channel`,`id`]);return{channelData:o,conversation:r,conversationType:r.conversationType,from:i,id:isNonEmptyString(n.id)?n.id:``,raw:n,recipient:a,serviceUrl:n.serviceUrl,tenantId:s,teamId:c,teamsChannelId:l}}function parseConversation(n){return!isObject(n)||!isNonEmptyString(n.id)?null:{conversationType:isNonEmptyString(n.conversationType)?n.conversationType:void 0,id:n.id,isGroup:typeof n.isGroup==`boolean`?n.isGroup:void 0,name:isNonEmptyString(n.name)?n.name:void 0,tenantId:isNonEmptyString(n.tenantId)?n.tenantId:void 0}}function parseChannelAccount(n){return!isObject(n)||!isNonEmptyString(n.id)?null:{aadObjectId:isNonEmptyString(n.aadObjectId)?n.aadObjectId:void 0,id:n.id,name:isNonEmptyString(n.name)?n.name:void 0,role:isNonEmptyString(n.role)?n.role:void 0}}function parseMentions(n){if(!Array.isArray(n))return[];let r=[];for(let i of n){if(!isObject(i)||i.type!==`mention`||!isNonEmptyString(i.text))continue;let n=parseChannelAccount(i.mentioned);n&&r.push({mentioned:n,text:i.text,type:`mention`})}return r}function parseAttachments(r){if(!Array.isArray(r))return[];let i=[];for(let a of r){if(!isObject(a)||!isNonEmptyString(a.contentType))continue;let r={content:isObject(a.content)?parseJsonObject(a.content):void 0,contentType:a.contentType,contentUrl:isNonEmptyString(a.contentUrl)?a.contentUrl:void 0,name:isNonEmptyString(a.name)?a.name:void 0};i.push(r)}return i}function inferScope(e){let t=e.conversationType;return t===`personal`||t===`groupChat`||t===`channel`?t:e.isGroup===!0?`groupChat`:e.isGroup===!1?`personal`:`unknown`}function readText(e){return typeof e.text==`string`?e.text:``}function stripBotMention(e,t,n){let r=e;for(let e of t)e.mentioned.id===n&&(r=r.replace(e.text,``));return r.trim()}function normalizeTeamsText(e){let t=e.trim();return looksLikeHtml(t)?getTurndownService().turndown(t.replace(/<at>(.*?)<\/at>/gi,`@$1`)).replace(/ {2}\n/g,`
3
- `).trim():decodeHtmlEntities(t)}function readNestedString(n,r){let i=n;for(let e of r){if(!isObject(i))return;i=i[e]}return isNonEmptyString(i)?i:void 0}function looksLikeHtml(e){return/<\/?[a-z][\s\S]*>/i.test(e)}function decodeHtmlEntities(e){return e.replace(HTML_ENTITY_PATTERN,e=>HTML_ENTITY_MAP[e.toLowerCase()]??e)}function getTurndownService(){return turndownService??=new TurndownService({bulletListMarker:`-`,codeBlockStyle:`fenced`,emDelimiter:`*`,headingStyle:`atx`,hr:`---`}),turndownService}export{formatTeamsContextBlock,isTeamsPersonalMessage,parseTeamsActivity,prependTeamsContext,teamsThreadRootActivityId};
2
+ `)}function parseMessageActivity(n){let r=parseActivityBase(n);if(!r)return null;let i=parseMentions(n.entities),a=normalizeTeamsText(stripBotMention(readText(n),i,r.recipient.id));return{...r,attachments:parseAttachments(n.attachments),isBotMentioned:i.some(e=>e.mentioned.id===r.recipient.id),mentions:i,replyToId:isNonEmptyString(n.replyToId)?n.replyToId:void 0,scope:inferScope(r.conversation),text:a,textFormat:isNonEmptyString(n.textFormat)?n.textFormat:void 0,type:`message`,value:isObject(n.value)?n.value:void 0}}function parseInvokeActivity(n){let r=parseActivityBase(n);return!r||!isNonEmptyString(n.name)?null:{...r,name:n.name,replyToId:isNonEmptyString(n.replyToId)?n.replyToId:void 0,scope:inferScope(r.conversation),type:`invoke`,value:isObject(n.value)?n.value:void 0}}function parseConversationUpdateActivity(e){let t=parseActivityBase(e);return t?{...t,type:`conversationUpdate`}:null}function parseActivityBase(n){if(!isNonEmptyString(n.serviceUrl))return null;let r=parseConversation(n.conversation),i=parseChannelAccount(n.from),a=parseChannelAccount(n.recipient);if(!r||!i||!a)return null;let o=isObject(n.channelData)?n.channelData:{},s=readNestedString(o,[`tenant`,`id`])??r.tenantId,c=readNestedString(o,[`team`,`id`]),l=readNestedString(o,[`channel`,`id`]);return{channelData:o,conversation:r,conversationType:r.conversationType,from:i,id:isNonEmptyString(n.id)?n.id:``,raw:n,recipient:a,serviceUrl:n.serviceUrl,tenantId:s,teamId:c,teamsChannelId:l}}function parseConversation(n){return!isObject(n)||!isNonEmptyString(n.id)?null:{conversationType:isNonEmptyString(n.conversationType)?n.conversationType:void 0,id:n.id,isGroup:typeof n.isGroup==`boolean`?n.isGroup:void 0,name:isNonEmptyString(n.name)?n.name:void 0,tenantId:isNonEmptyString(n.tenantId)?n.tenantId:void 0}}function parseChannelAccount(n){return!isObject(n)||!isNonEmptyString(n.id)?null:{aadObjectId:isNonEmptyString(n.aadObjectId)?n.aadObjectId:void 0,id:n.id,name:isNonEmptyString(n.name)?n.name:void 0,role:isNonEmptyString(n.role)?n.role:void 0}}function parseMentions(n){if(!Array.isArray(n))return[];let r=[];for(let i of n){if(!isObject(i)||i.type!==`mention`||!isNonEmptyString(i.text))continue;let n=parseChannelAccount(i.mentioned);n&&r.push({mentioned:n,text:i.text,type:`mention`})}return r}function parseAttachments(r){if(!Array.isArray(r))return[];let i=[];for(let a of r){if(!isObject(a)||!isNonEmptyString(a.contentType))continue;let r={content:isObject(a.content)?parseJsonObject(a.content):void 0,contentType:a.contentType,contentUrl:isNonEmptyString(a.contentUrl)?a.contentUrl:void 0,name:isNonEmptyString(a.name)?a.name:void 0};i.push(r)}return i}function inferScope(e){let t=e.conversationType;return t===`personal`||t===`groupChat`||t===`channel`?t:e.isGroup===!0?`groupChat`:e.isGroup===!1?`personal`:`unknown`}function readText(e){return typeof e.text==`string`?e.text:``}function stripBotMention(e,t,n){let r=e;for(let e of t)e.mentioned.id===n&&(r=r.replace(e.text,``));return r.trim()}function normalizeTeamsText(e){let t=e.trim();return looksLikeHtml(t)?getTurndownService().turndown(t.replace(/<at>(.*?)<\/at>/gi,`@$1`)).replace(/ {2}\n/g,`
3
+ `).trim():decodeHtmlEntities(t)}function readNestedString(n,r){let i=n;for(let e of r){if(!isObject(i))return;i=i[e]}return isNonEmptyString(i)?i:void 0}function looksLikeHtml(e){return/<\/?[a-z][\s\S]*>/i.test(e)}function decodeHtmlEntities(e){return e.replace(HTML_ENTITY_PATTERN,e=>HTML_ENTITY_MAP[e.toLowerCase()]??e)}function getTurndownService(){return turndownService??=new TurndownService({bulletListMarker:`-`,codeBlockStyle:`fenced`,emDelimiter:`*`,headingStyle:`atx`,hr:`---`}),turndownService}export{formatTeamsContextBlock,isTeamsPersonalMessage,parseTeamsActivity,teamsThreadRootActivityId};
@@ -1,7 +1,7 @@
1
1
  export type { ModelMessage } from "ai";
2
2
  export { teamsChannel, type TeamsChannel, type TeamsChannelConfig, type TeamsChannelCredentials, type TeamsChannelEvents, type TeamsChannelState, type TeamsContext, type TeamsEventContext, type TeamsHandle, type TeamsInboundResult, type TeamsInboundResultOrPromise, type TeamsInvokeResult, type TeamsInvokeResultOrPromise, type TeamsReceiveArgs, type TeamsRequestOptions, type TeamsThread, } from "#public/channels/teams/teamsChannel.js";
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
- export { formatTeamsContextBlock, isTeamsPersonalMessage, parseTeamsActivity, prependTeamsContext, teamsThreadRootActivityId, type TeamsActivity, type TeamsActivityBase, type TeamsConversationAccount, type TeamsConversationScope, type TeamsConversationUpdateActivity, type TeamsInboundContext, type TeamsInvokeActivity, type TeamsMessageActivity, } from "#public/channels/teams/inbound.js";
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";
6
6
  export { collectTeamsFileParts, buildTeamsTurnMessage, createTeamsFetchFile, normalizeTeamsFilesPolicy, type TeamsFilesConfig, type TeamsFilesPolicy, } from "#public/channels/teams/attachments.js";
7
7
  export { buildAuthCompletedText, defaultTeamsAuth, formatConnectionDisplayName, teamsMentionUser, } from "#public/channels/teams/defaults.js";
@@ -1 +1 @@
1
- import{TEAMS_MESSAGE_TEXT_MAX_LENGTH,callTeamsConnectorApi,normalizeTeamsPostInput,replyToTeamsActivity,resolveTeamsAccessToken,resolveTeamsAppId,resolveTeamsAppPassword,resolveTeamsTenantId,sendTeamsActivity,splitTeamsMessageText,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,TEAMS_HITL_CHOICE_INPUT_ID,TEAMS_HITL_DATA_KEY,TEAMS_HITL_FREEFORM_INPUT_ID,deriveTeamsInputResponses,isTeamsInputResponseActivity,renderAnsweredInputRequestMessage,renderInputRequestAttachment,renderInputRequestMessage,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{teamsChannel}from"#public/channels/teams/teamsChannel.js";import{formatTeamsContextBlock,isTeamsPersonalMessage,parseTeamsActivity,prependTeamsContext,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{buildAuthCompletedText,defaultTeamsAuth,formatConnectionDisplayName,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsJwt,verifyTeamsRequest}from"#public/channels/teams/verify.js";export{TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,TEAMS_HITL_CHOICE_INPUT_ID,TEAMS_HITL_DATA_KEY,TEAMS_HITL_FREEFORM_INPUT_ID,TEAMS_MESSAGE_TEXT_MAX_LENGTH,buildAuthCompletedText,buildTeamsTurnMessage,callTeamsConnectorApi,collectTeamsFileParts,createTeamsFetchFile,defaultTeamsAuth,deriveTeamsInputResponses,formatConnectionDisplayName,formatTeamsContextBlock,isTeamsInputResponseActivity,isTeamsPersonalMessage,normalizeTeamsFilesPolicy,normalizeTeamsPostInput,parseTeamsActivity,prependTeamsContext,renderAnsweredInputRequestMessage,renderInputRequestAttachment,renderInputRequestMessage,replyToTeamsActivity,resolveTeamsAccessToken,resolveTeamsAppId,resolveTeamsAppPassword,resolveTeamsTenantId,sendTeamsActivity,splitTeamsMessageText,teamsChannel,teamsContinuationToken,teamsInvokeResponse,teamsMentionUser,teamsThreadRootActivityId,triggerTeamsTypingIndicator,updateTeamsActivity,verifyTeamsJwt,verifyTeamsRequest};
1
+ import{TEAMS_MESSAGE_TEXT_MAX_LENGTH,callTeamsConnectorApi,normalizeTeamsPostInput,replyToTeamsActivity,resolveTeamsAccessToken,resolveTeamsAppId,resolveTeamsAppPassword,resolveTeamsTenantId,sendTeamsActivity,splitTeamsMessageText,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,TEAMS_HITL_CHOICE_INPUT_ID,TEAMS_HITL_DATA_KEY,TEAMS_HITL_FREEFORM_INPUT_ID,deriveTeamsInputResponses,isTeamsInputResponseActivity,renderAnsweredInputRequestMessage,renderInputRequestAttachment,renderInputRequestMessage,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{teamsChannel}from"#public/channels/teams/teamsChannel.js";import{formatTeamsContextBlock,isTeamsPersonalMessage,parseTeamsActivity,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{buildAuthCompletedText,defaultTeamsAuth,formatConnectionDisplayName,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsJwt,verifyTeamsRequest}from"#public/channels/teams/verify.js";export{TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,TEAMS_HITL_CHOICE_INPUT_ID,TEAMS_HITL_DATA_KEY,TEAMS_HITL_FREEFORM_INPUT_ID,TEAMS_MESSAGE_TEXT_MAX_LENGTH,buildAuthCompletedText,buildTeamsTurnMessage,callTeamsConnectorApi,collectTeamsFileParts,createTeamsFetchFile,defaultTeamsAuth,deriveTeamsInputResponses,formatConnectionDisplayName,formatTeamsContextBlock,isTeamsInputResponseActivity,isTeamsPersonalMessage,normalizeTeamsFilesPolicy,normalizeTeamsPostInput,parseTeamsActivity,renderAnsweredInputRequestMessage,renderInputRequestAttachment,renderInputRequestMessage,replyToTeamsActivity,resolveTeamsAccessToken,resolveTeamsAppId,resolveTeamsAppPassword,resolveTeamsTenantId,sendTeamsActivity,splitTeamsMessageText,teamsChannel,teamsContinuationToken,teamsInvokeResponse,teamsMentionUser,teamsThreadRootActivityId,triggerTeamsTypingIndicator,updateTeamsActivity,verifyTeamsJwt,verifyTeamsRequest};
@@ -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{parseTeamsActivity,prependTeamsContext,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{defaultEvents,defaultOnMessage,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),a=e.onMessage??defaultOnMessage,o={...defaultEvents,...e.events};return defineChannel({kindHint:`teams`,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t),context(t,n){return rebuildTeamsContext(t,n,e)},routes:[POST(e.route??`/ash/v1/teams`,async(r,{send:i,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Teams body is not valid JSON`,{error:e}),teamsOk()}let l=parseTeamsActivity(c);return l===null?teamsOk():l.type===`message`?(o(dispatchMessage({activity:l,config:e,filesPolicy:t,onMessage:a,send:i})),teamsOk()):l.type===`invoke`?handleInvoke({activity:l,config:e,send:i,waitUntil:o}):teamsOk()})],async receive(t,{send:n}){let r=t.args,i=readString(r.serviceUrl),a=readString(r.conversationId);if(!i||!a)throw Error(`teamsChannel().receive requires args.serviceUrl and args.conversationId.`);let o=readString(r.conversationType)??null,s=readString(r.replyToActivityId)??null,c=r.initialMessage;if(c!==void 0&&s!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let u={...initialTeamsState(),channelId:readString(r.channelId)??null,conversationId:a,conversationType:o,replyToActivityId:s,serviceUrl:i,teamId:readString(r.teamId)??null,tenantId:readString(r.tenantId)??null};if(c!==void 0){let t=await buildTeamsBinding({config:e,state:u}).thread.post(c);o!==`personal`&&t.id&&(s=t.id,u.replyToActivityId=t.id)}return n(t.message,{auth:t.auth,continuationToken:teamsContinuationToken({conversationId:a,replyToActivityId:s,tenantId:u.tenantId}),state:u})},events:o})}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,state:e}}function buildTeamsBinding(e){let n=buildTeamsHandle(e);return{teams:n,thread:{mentionUser:teamsMentionUser,post(e){return n.sendActivity(e)},async startTyping(){try{await n.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,t){return n.updateActivity(e,t)}}}}function buildTeamsHandle(e){let t=e.state,n=e.config.api,r=e.config.credentials;function requireAddress(){let e=t.conversationId??``,n=t.serviceUrl??``;if(!e||!n)throw Error(`teamsChannel: missing serviceUrl or conversationId for outbound message.`);return{conversationId:e,serviceUrl:n}}function anchor(n){if(!n.id||t.replyToActivityId||t.conversationType===`personal`)return;t.replyToActivityId=n.id;let r=t.conversationId;r&&e.session?.setContinuationToken(teamsContinuationToken({conversationId:r,replyToActivityId:n.id,tenantId:t.tenantId}))}async function send(e){let i=requireAddress(),a=buildOutboundActivity(t,e),o=t.replyToActivityId===null?await sendTeamsActivity({...n,body:a,credentials:r,conversationId:i.conversationId,serviceUrl:i.serviceUrl}):await replyToTeamsActivity({...n,body:a,credentials:r,activityId:t.replyToActivityId,conversationId:i.conversationId,serviceUrl:i.serviceUrl});return anchor(o),o}return{channelId:t.channelId??void 0,conversationId:t.conversationId??``,conversationType:t.conversationType??void 0,replyToActivityId:t.replyToActivityId??void 0,serviceUrl:t.serviceUrl??``,teamId:t.teamId??void 0,tenantId:t.tenantId??void 0,request(e,t,i){let o=requireAddress();return callTeamsConnectorApi({...n,body:t,credentials:r,method:i?.method,path:e,serviceUrl:o.serviceUrl})},sendActivity:send,replyToActivity(e){let i=requireAddress(),a=t.replyToActivityId??``;if(!a)throw Error(`teamsChannel: missing reply activity id.`);return replyToTeamsActivity({...n,body:buildOutboundActivity(t,e),credentials:r,activityId:a,conversationId:i.conversationId,serviceUrl:i.serviceUrl})},updateActivity(e,i){let a=requireAddress();return updateTeamsActivity({...n,body:buildOutboundActivity(t,i),credentials:r,activityId:e,conversationId:a.conversationId,serviceUrl:a.serviceUrl})},async startTyping(){let e=requireAddress();await triggerTeamsTypingIndicator({...n,credentials:r,conversationId:e.conversationId,serviceUrl:e.serviceUrl})}}}async function verifyInbound(e,t){try{return await verifyTeamsRequest(e,{appId:t?.webhookVerifier?void 0:t?.appId,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`teams inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r;try{r=await e.onMessage(n,e.activity)}catch(e){log.error(`Teams message handler failed`,{error:e});return}if(r==null)return;let i=collectTeamsFileParts(e.activity.attachments,e.filesPolicy),a=prependTeamsContext(buildTeamsTurnMessage(e.activity.text,i),{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});try{await e.send({message:a,context:r.context},{auth:r.auth,continuationToken:stateToken(t),state:t})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){if(isTeamsInputResponseActivity(e.activity))return e.waitUntil(dispatchInputResponses({activity:e.activity,send:e.send})),Response.json(teamsInvokeResponse());if(e.config.onInvoke===void 0)return teamsOk();let t=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),n=await e.config.onInvoke(t,e.activity);return n instanceof Response?n:n&&typeof n==`object`?Response.json(n):teamsOk()}async function dispatchInputResponses(e){let t=deriveTeamsInputResponses(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity);try{await e.send({inputResponses:t},{auth:null,continuationToken:stateToken(n),state:n})}catch(e){log.error(`Teams input response delivery failed`,{error:e})}}function stateFromActivity(e){return{bot:e.recipient,channelId:e.teamsChannelId??null,conversationId:e.conversation.id,conversationType:e.conversationType??e.scope,pendingAuthActivityId:null,replyToActivityId:teamsThreadRootActivityId(e),serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,pendingAuthActivityId:null,replyToActivityId:null,serviceUrl:null,teamId:null,tenantId:null,triggeringUser:null}}function stateToken(e){let t=e.conversationId??``;if(!t)throw Error(`teamsChannel: missing conversation id.`);return teamsContinuationToken({conversationId:t,replyToActivityId:e.replyToActivityId,tenantId:e.tenantId})}function buildOutboundActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)return t;let n=normalizeTeamsPostInput(t),r=mergeChannelData(e,n.channelData);return{...n,channelData:r,conversation:e.conversationId?{id:e.conversationId}:void 0,from:e.bot??void 0,replyToId:e.replyToActivityId??void 0,type:`message`}}function mergeChannelData(e,t){let r={...t};return e.tenantId&&(r.tenant={id:e.tenantId}),e.teamId&&(r.team={id:e.teamId}),e.channelId&&(r.channel={id:e.channelId}),Object.keys(r).length>0?parseJsonObject(r):void 0}function teamsOk(){return new Response(`ok`,{status:200})}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{teamsChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{callTeamsConnectorApi,normalizeTeamsPostInput,replyToTeamsActivity,sendTeamsActivity,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{deriveTeamsInputResponses,isTeamsInputResponseActivity,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{formatTeamsContextBlock,parseTeamsActivity,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{defaultEvents,defaultOnMessage,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),a=e.onMessage??defaultOnMessage,o={...defaultEvents,...e.events};return defineChannel({kindHint:`teams`,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t),context(t,n){return rebuildTeamsContext(t,n,e)},routes:[POST(e.route??`/ash/v1/teams`,async(r,{send:i,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Teams body is not valid JSON`,{error:e}),teamsOk()}let l=parseTeamsActivity(c);return l===null?teamsOk():l.type===`message`?(o(dispatchMessage({activity:l,config:e,filesPolicy:t,onMessage:a,send:i})),teamsOk()):l.type===`invoke`?handleInvoke({activity:l,config:e,send:i,waitUntil:o}):teamsOk()})],async receive(t,{send:n}){let r=t.args,i=readString(r.serviceUrl),a=readString(r.conversationId);if(!i||!a)throw Error(`teamsChannel().receive requires args.serviceUrl and args.conversationId.`);let o=readString(r.conversationType)??null,s=readString(r.replyToActivityId)??null,c=r.initialMessage;if(c!==void 0&&s!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let u={...initialTeamsState(),channelId:readString(r.channelId)??null,conversationId:a,conversationType:o,replyToActivityId:s,serviceUrl:i,teamId:readString(r.teamId)??null,tenantId:readString(r.tenantId)??null};if(c!==void 0){let t=await buildTeamsBinding({config:e,state:u}).thread.post(c);o!==`personal`&&t.id&&(s=t.id,u.replyToActivityId=t.id)}return n(t.message,{auth:t.auth,continuationToken:teamsContinuationToken({conversationId:a,replyToActivityId:s,tenantId:u.tenantId}),state:u})},events:o})}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,state:e}}function buildTeamsBinding(e){let n=buildTeamsHandle(e);return{teams:n,thread:{mentionUser:teamsMentionUser,post(e){return n.sendActivity(e)},async startTyping(){try{await n.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,t){return n.updateActivity(e,t)}}}}function buildTeamsHandle(e){let t=e.state,n=e.config.api,r=e.config.credentials;function requireAddress(){let e=t.conversationId??``,n=t.serviceUrl??``;if(!e||!n)throw Error(`teamsChannel: missing serviceUrl or conversationId for outbound message.`);return{conversationId:e,serviceUrl:n}}function anchor(n){if(!n.id||t.replyToActivityId||t.conversationType===`personal`)return;t.replyToActivityId=n.id;let r=t.conversationId;r&&e.session?.setContinuationToken(teamsContinuationToken({conversationId:r,replyToActivityId:n.id,tenantId:t.tenantId}))}async function send(e){let i=requireAddress(),a=buildOutboundActivity(t,e),o=t.replyToActivityId===null?await sendTeamsActivity({...n,body:a,credentials:r,conversationId:i.conversationId,serviceUrl:i.serviceUrl}):await replyToTeamsActivity({...n,body:a,credentials:r,activityId:t.replyToActivityId,conversationId:i.conversationId,serviceUrl:i.serviceUrl});return anchor(o),o}return{channelId:t.channelId??void 0,conversationId:t.conversationId??``,conversationType:t.conversationType??void 0,replyToActivityId:t.replyToActivityId??void 0,serviceUrl:t.serviceUrl??``,teamId:t.teamId??void 0,tenantId:t.tenantId??void 0,request(e,t,i){let o=requireAddress();return callTeamsConnectorApi({...n,body:t,credentials:r,method:i?.method,path:e,serviceUrl:o.serviceUrl})},sendActivity:send,replyToActivity(e){let i=requireAddress(),a=t.replyToActivityId??``;if(!a)throw Error(`teamsChannel: missing reply activity id.`);return replyToTeamsActivity({...n,body:buildOutboundActivity(t,e),credentials:r,activityId:a,conversationId:i.conversationId,serviceUrl:i.serviceUrl})},updateActivity(e,i){let a=requireAddress();return updateTeamsActivity({...n,body:buildOutboundActivity(t,i),credentials:r,activityId:e,conversationId:a.conversationId,serviceUrl:a.serviceUrl})},async startTyping(){let e=requireAddress();await triggerTeamsTypingIndicator({...n,credentials:r,conversationId:e.conversationId,serviceUrl:e.serviceUrl})}}}async function verifyInbound(e,t){try{return await verifyTeamsRequest(e,{appId:t?.webhookVerifier?void 0:t?.appId,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`teams inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r;try{r=await e.onMessage(n,e.activity)}catch(e){log.error(`Teams message handler failed`,{error:e});return}if(r==null)return;let i=collectTeamsFileParts(e.activity.attachments,e.filesPolicy),a=buildTeamsTurnMessage(e.activity.text,i),o={activityId:e.activity.id,channelId:e.activity.teamsChannelId,conversationId:e.activity.conversation.id,conversationType:e.activity.conversationType,scope:e.activity.scope,teamId:e.activity.teamId,tenantId:e.activity.tenantId,userId:e.activity.from.id,userName:e.activity.from.name},s=r.context??[];try{await e.send({message:a,context:[formatTeamsContextBlock(o),...s]},{auth:r.auth,continuationToken:stateToken(t),state:t})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){if(isTeamsInputResponseActivity(e.activity))return e.waitUntil(dispatchInputResponses({activity:e.activity,send:e.send})),Response.json(teamsInvokeResponse());if(e.config.onInvoke===void 0)return teamsOk();let t=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),n=await e.config.onInvoke(t,e.activity);return n instanceof Response?n:n&&typeof n==`object`?Response.json(n):teamsOk()}async function dispatchInputResponses(e){let t=deriveTeamsInputResponses(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity);try{await e.send({inputResponses:t},{auth:null,continuationToken:stateToken(n),state:n})}catch(e){log.error(`Teams input response delivery failed`,{error:e})}}function stateFromActivity(e){return{bot:e.recipient,channelId:e.teamsChannelId??null,conversationId:e.conversation.id,conversationType:e.conversationType??e.scope,pendingAuthActivityId:null,replyToActivityId:teamsThreadRootActivityId(e),serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,pendingAuthActivityId:null,replyToActivityId:null,serviceUrl:null,teamId:null,tenantId:null,triggeringUser:null}}function stateToken(e){let t=e.conversationId??``;if(!t)throw Error(`teamsChannel: missing conversation id.`);return teamsContinuationToken({conversationId:t,replyToActivityId:e.replyToActivityId,tenantId:e.tenantId})}function buildOutboundActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)return t;let n=normalizeTeamsPostInput(t),r=mergeChannelData(e,n.channelData);return{...n,channelData:r,conversation:e.conversationId?{id:e.conversationId}:void 0,from:e.bot??void 0,replyToId:e.replyToActivityId??void 0,type:`message`}}function mergeChannelData(e,t){let r={...t};return e.tenantId&&(r.tenant={id:e.tenantId}),e.teamId&&(r.team={id:e.teamId}),e.channelId&&(r.channel={id:e.channelId}),Object.keys(r).length>0?parseJsonObject(r):void 0}function teamsOk(){return new Response(`ok`,{status:200})}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{teamsChannel};
@@ -4,7 +4,6 @@
4
4
  * The channel owns small, documented data shapes instead of exposing
5
5
  * Telegram's raw webhook payloads as the primary public API.
6
6
  */
7
- import type { UserContent } from "ai";
8
7
  /** Telegram chat types handled by the native channel. */
9
8
  export type TelegramChatType = "channel" | "group" | "private" | "supergroup";
10
9
  /** Telegram user metadata surfaced by inbound messages and callbacks. */
@@ -84,5 +83,3 @@ export interface TelegramInboundContext {
84
83
  export declare function parseTelegramUpdate(value: unknown): TelegramUpdate | null;
85
84
  /** Renders one {@link TelegramInboundContext} as a deterministic context block. */
86
85
  export declare function formatTelegramContextBlock(context: TelegramInboundContext): string;
87
- /** Prepends a `<telegram_context>` block to the inbound turn message. */
88
- export declare function prependTelegramContext(message: string | UserContent, context: TelegramInboundContext): string | UserContent;
@@ -1,2 +1,2 @@
1
1
  import{isNonEmptyString,isObject}from"#shared/guards.js";function parseTelegramUpdate(e){if(!isObject(e))return null;let n=parseTelegramMessage(e.message);if(n!==null)return{kind:`message`,message:n};let r=parseTelegramCallbackQuery(e.callback_query);return r===null?null:{callbackQuery:r,kind:`callback_query`}}function formatTelegramContextBlock(e){return[`<telegram_context>`,`response_medium: telegram`,`response_instructions: Reply for Telegram in concise plain text. Avoid tables, long code fences, and formatting that depends on Markdown rendering.`,`chat_id: ${e.chatId}`,`chat_type: ${e.chatType}`,...e.chatTitle?[`chat_title: ${e.chatTitle}`]:[],`message_id: ${e.messageId}`,...e.messageThreadId===void 0?[]:[`message_thread_id: ${e.messageThreadId}`],...e.userId?[`user_id: ${e.userId}`]:[],...e.username?[`username: ${e.username}`]:[],...e.botUsername?[`bot_username: ${e.botUsername}`]:[],`</telegram_context>`].join(`
2
- `)}function prependTelegramContext(e,t){let n=formatTelegramContextBlock(t);return typeof e==`string`?e.length>0?`${n}\n\n${e}`:n:[{type:`text`,text:n},...e]}function parseTelegramMessage(e){if(!isObject(e))return null;let n=parseTelegramChat(e.chat),r=numberLikeToString(e.message_id);return!n||!r?null:{attachments:parseAttachments(e),caption:typeof e.caption==`string`?e.caption:``,chat:n,from:parseTelegramUser(e.from),messageId:r,messageThreadId:typeof e.message_thread_id==`number`?e.message_thread_id:void 0,raw:e,replyToMessage:parseMessageReference(e.reply_to_message),text:typeof e.text==`string`?e.text:``}}function parseTelegramCallbackQuery(n){if(!isObject(n)||!isNonEmptyString(n.id))return null;let r=parseTelegramUser(n.from);return r?{data:typeof n.data==`string`?n.data:void 0,from:r,id:n.id,message:parseMessageReference(n.message),raw:n}:null}function parseMessageReference(e){if(!isObject(e))return;let n=parseTelegramChat(e.chat),r=numberLikeToString(e.message_id);if(!(!n||!r))return{chat:n,from:parseTelegramUser(e.from),messageId:r,messageThreadId:typeof e.message_thread_id==`number`?e.message_thread_id:void 0}}function parseTelegramChat(e){if(!isObject(e))return null;let n=numberLikeToString(e.id),r=parseChatType(e.type);return!n||!r?null:{id:n,title:typeof e.title==`string`?e.title:void 0,type:r,username:typeof e.username==`string`?e.username:void 0}}function parseTelegramUser(e){if(!isObject(e))return;let n=numberLikeToString(e.id);if(n)return{firstName:typeof e.first_name==`string`?e.first_name:void 0,id:n,isBot:e.is_bot===!0,languageCode:typeof e.language_code==`string`?e.language_code:void 0,lastName:typeof e.last_name==`string`?e.last_name:void 0,username:typeof e.username==`string`?e.username:void 0}}function parseAttachments(e){let t=[],n=parseLargestPhoto(e.photo);n!==null&&t.push(n);let r=parseDocument(e.document);return r!==null&&t.push(r),t}function parseLargestPhoto(e){if(!Array.isArray(e)||e.length===0)return null;let n=e.filter(isObject).map(e=>({fileId:typeof e.file_id==`string`?e.file_id:``,fileUniqueId:typeof e.file_unique_id==`string`?e.file_unique_id:void 0,height:typeof e.height==`number`?e.height:void 0,size:typeof e.file_size==`number`?e.file_size:void 0,width:typeof e.width==`number`?e.width:void 0})).filter(e=>e.fileId.length>0).sort((e,t)=>scorePhoto(t)-scorePhoto(e))[0];return n?{fileId:n.fileId,fileName:`photo.jpg`,fileUniqueId:n.fileUniqueId,height:n.height,kind:`photo`,mediaType:`image/jpeg`,size:n.size,width:n.width}:null}function parseDocument(e){return!isObject(e)||typeof e.file_id!=`string`?null:{fileId:e.file_id,fileName:typeof e.file_name==`string`?e.file_name:void 0,fileUniqueId:typeof e.file_unique_id==`string`?e.file_unique_id:void 0,kind:`document`,mediaType:typeof e.mime_type==`string`?e.mime_type:void 0,size:typeof e.file_size==`number`?e.file_size:void 0}}function scorePhoto(e){return e.size===void 0?(e.width??0)*(e.height??0):e.size}function parseChatType(e){return e===`channel`||e===`group`||e===`private`||e===`supergroup`?e:null}function numberLikeToString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{formatTelegramContextBlock,parseTelegramUpdate,prependTelegramContext};
2
+ `)}function parseTelegramMessage(e){if(!isObject(e))return null;let n=parseTelegramChat(e.chat),r=numberLikeToString(e.message_id);return!n||!r?null:{attachments:parseAttachments(e),caption:typeof e.caption==`string`?e.caption:``,chat:n,from:parseTelegramUser(e.from),messageId:r,messageThreadId:typeof e.message_thread_id==`number`?e.message_thread_id:void 0,raw:e,replyToMessage:parseMessageReference(e.reply_to_message),text:typeof e.text==`string`?e.text:``}}function parseTelegramCallbackQuery(n){if(!isObject(n)||!isNonEmptyString(n.id))return null;let r=parseTelegramUser(n.from);return r?{data:typeof n.data==`string`?n.data:void 0,from:r,id:n.id,message:parseMessageReference(n.message),raw:n}:null}function parseMessageReference(e){if(!isObject(e))return;let n=parseTelegramChat(e.chat),r=numberLikeToString(e.message_id);if(!(!n||!r))return{chat:n,from:parseTelegramUser(e.from),messageId:r,messageThreadId:typeof e.message_thread_id==`number`?e.message_thread_id:void 0}}function parseTelegramChat(e){if(!isObject(e))return null;let n=numberLikeToString(e.id),r=parseChatType(e.type);return!n||!r?null:{id:n,title:typeof e.title==`string`?e.title:void 0,type:r,username:typeof e.username==`string`?e.username:void 0}}function parseTelegramUser(e){if(!isObject(e))return;let n=numberLikeToString(e.id);if(n)return{firstName:typeof e.first_name==`string`?e.first_name:void 0,id:n,isBot:e.is_bot===!0,languageCode:typeof e.language_code==`string`?e.language_code:void 0,lastName:typeof e.last_name==`string`?e.last_name:void 0,username:typeof e.username==`string`?e.username:void 0}}function parseAttachments(e){let t=[],n=parseLargestPhoto(e.photo);n!==null&&t.push(n);let r=parseDocument(e.document);return r!==null&&t.push(r),t}function parseLargestPhoto(e){if(!Array.isArray(e)||e.length===0)return null;let n=e.filter(isObject).map(e=>({fileId:typeof e.file_id==`string`?e.file_id:``,fileUniqueId:typeof e.file_unique_id==`string`?e.file_unique_id:void 0,height:typeof e.height==`number`?e.height:void 0,size:typeof e.file_size==`number`?e.file_size:void 0,width:typeof e.width==`number`?e.width:void 0})).filter(e=>e.fileId.length>0).sort((e,t)=>scorePhoto(t)-scorePhoto(e))[0];return n?{fileId:n.fileId,fileName:`photo.jpg`,fileUniqueId:n.fileUniqueId,height:n.height,kind:`photo`,mediaType:`image/jpeg`,size:n.size,width:n.width}:null}function parseDocument(e){return!isObject(e)||typeof e.file_id!=`string`?null:{fileId:e.file_id,fileName:typeof e.file_name==`string`?e.file_name:void 0,fileUniqueId:typeof e.file_unique_id==`string`?e.file_unique_id:void 0,kind:`document`,mediaType:typeof e.mime_type==`string`?e.mime_type:void 0,size:typeof e.file_size==`number`?e.file_size:void 0}}function scorePhoto(e){return e.size===void 0?(e.width??0)*(e.height??0):e.size}function parseChatType(e){return e===`channel`||e===`group`||e===`private`||e===`supergroup`?e:null}function numberLikeToString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{formatTelegramContextBlock,parseTelegramUpdate};
@@ -1,7 +1,7 @@
1
1
  export type { ModelMessage } from "ai";
2
2
  export { telegramChannel, type TelegramChannel, type TelegramChannelConfig, type TelegramChannelCredentials, type TelegramChannelEvents, type TelegramChannelState, type TelegramContext, type TelegramEventContext, type TelegramHandle, type TelegramInboundResult, type TelegramInboundResultOrPromise, type TelegramReceiveArgs, } from "#public/channels/telegram/telegramChannel.js";
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
- export { formatTelegramContextBlock, parseTelegramUpdate, prependTelegramContext, type TelegramAttachment, type TelegramCallbackQuery, type TelegramChat, type TelegramChatType, type TelegramInboundContext, type TelegramMessage, type TelegramMessageReference, type TelegramUpdate, type TelegramUser, } from "#public/channels/telegram/inbound.js";
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";
6
6
  export { TELEGRAM_FILE_URL_PROTOCOL, buildTelegramTurnMessage, collectTelegramFileParts, createTelegramFetchFile, createTelegramFileUrl, } from "#public/channels/telegram/attachments.js";
7
7
  export { defaultTelegramAuth } from "#public/channels/telegram/defaults.js";
@@ -1 +1 @@
1
- import{TELEGRAM_MESSAGE_TEXT_MAX_LENGTH,answerTelegramCallbackQuery,callTelegramApi,downloadTelegramFile,editTelegramMessageReplyMarkup,getTelegramFile,resolveTelegramBotToken,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_CALLBACK_RESPONSE_PREFIX,TELEGRAM_HITL_CALLBACK_PREFIX,TELEGRAM_REPLY_RESPONSE_PREFIX,isTelegramSyntheticResponse,registerTelegramFreeformPrompt,renderTelegramInputRequest,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{telegramChannel}from"#public/channels/telegram/telegramChannel.js";import{formatTelegramContextBlock,parseTelegramUpdate,prependTelegramContext}from"#public/channels/telegram/inbound.js";import{TELEGRAM_FILE_URL_PROTOCOL,buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile,createTelegramFileUrl}from"#public/channels/telegram/attachments.js";import{defaultTelegramAuth}from"#public/channels/telegram/defaults.js";import{resolveTelegramWebhookSecretToken,verifyTelegramRequest}from"#public/channels/telegram/verify.js";export{TELEGRAM_CALLBACK_RESPONSE_PREFIX,TELEGRAM_FILE_URL_PROTOCOL,TELEGRAM_HITL_CALLBACK_PREFIX,TELEGRAM_MESSAGE_TEXT_MAX_LENGTH,TELEGRAM_REPLY_RESPONSE_PREFIX,answerTelegramCallbackQuery,buildTelegramTurnMessage,callTelegramApi,collectTelegramFileParts,createTelegramFetchFile,createTelegramFileUrl,defaultTelegramAuth,downloadTelegramFile,editTelegramMessageReplyMarkup,formatTelegramContextBlock,getTelegramFile,isTelegramSyntheticResponse,parseTelegramUpdate,prependTelegramContext,registerTelegramFreeformPrompt,renderTelegramInputRequest,resolveTelegramBotToken,resolveTelegramInputResponses,resolveTelegramWebhookSecretToken,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramCallbackInputResponse,telegramChannel,telegramContinuationToken,telegramReplyInputResponse,verifyTelegramRequest};
1
+ import{TELEGRAM_MESSAGE_TEXT_MAX_LENGTH,answerTelegramCallbackQuery,callTelegramApi,downloadTelegramFile,editTelegramMessageReplyMarkup,getTelegramFile,resolveTelegramBotToken,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_CALLBACK_RESPONSE_PREFIX,TELEGRAM_HITL_CALLBACK_PREFIX,TELEGRAM_REPLY_RESPONSE_PREFIX,isTelegramSyntheticResponse,registerTelegramFreeformPrompt,renderTelegramInputRequest,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{telegramChannel}from"#public/channels/telegram/telegramChannel.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{TELEGRAM_FILE_URL_PROTOCOL,buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile,createTelegramFileUrl}from"#public/channels/telegram/attachments.js";import{defaultTelegramAuth}from"#public/channels/telegram/defaults.js";import{resolveTelegramWebhookSecretToken,verifyTelegramRequest}from"#public/channels/telegram/verify.js";export{TELEGRAM_CALLBACK_RESPONSE_PREFIX,TELEGRAM_FILE_URL_PROTOCOL,TELEGRAM_HITL_CALLBACK_PREFIX,TELEGRAM_MESSAGE_TEXT_MAX_LENGTH,TELEGRAM_REPLY_RESPONSE_PREFIX,answerTelegramCallbackQuery,buildTelegramTurnMessage,callTelegramApi,collectTelegramFileParts,createTelegramFetchFile,createTelegramFileUrl,defaultTelegramAuth,downloadTelegramFile,editTelegramMessageReplyMarkup,formatTelegramContextBlock,getTelegramFile,isTelegramSyntheticResponse,parseTelegramUpdate,registerTelegramFreeformPrompt,renderTelegramInputRequest,resolveTelegramBotToken,resolveTelegramInputResponses,resolveTelegramWebhookSecretToken,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramCallbackInputResponse,telegramChannel,telegramContinuationToken,telegramReplyInputResponse,verifyTelegramRequest};
@@ -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{parseTelegramUpdate,prependTelegramContext}from"#public/channels/telegram/inbound.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},c=defineChannel({kindHint:`telegram`,state:initialTelegramState(e.botUsername),fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/ash/v1/telegram`,async(r,{send:a,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:n,send:a,uploadPolicy:t})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,send:a})),new Response(`ok`))})],async receive(t,{send:n}){let r=t.args,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires args.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readOptionalString(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=o;return s!==void 0&&(c=(await buildTelegramHandle({config:e,state:{...initialTelegramState(e.botUsername),chatId:i,messageThreadId:a??null}}).sendMessage(s)).id||void 0),n(t.message,{auth:t.auth,continuationToken:telegramContinuationToken({chatId:i,conversationId:c,messageThreadId:a}),state:{...initialTelegramState(e.botUsername),chatId:i,conversationId:c??null,messageThreadId:a??null}})},events:r});return attachTelegramDeliver(c),c}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let n=e.config.api,r=e.state,i=e.config.credentials;function anchor(t){!t.id||r.chatType===`private`||(r.conversationId=t.id,r.chatId&&e.session?.setContinuationToken(telegramContinuationToken({chatId:r.chatId,conversationId:t.id,messageThreadId:r.messageThreadId??void 0})))}async function sendOne(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for outbound message.`);let a=await sendTelegramMessage({apiBaseUrl:n?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??r.messageThreadId??void 0},credentials:i,fetch:n?.fetch,fileBaseUrl:n?.fileBaseUrl,chatId:t});return anchor(a),a}return{botUsername:r.botUsername??e.config.botUsername,chatId:r.chatId??``,chatType:r.chatType??void 0,conversationId:r.conversationId??void 0,messageThreadId:r.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:n?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:i,fetch:n?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:n?.apiBaseUrl,chatId:t,credentials:i,fetch:n?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},post(e){return postTelegramMessage(e,sendOne)},request(e,t){return callTelegramApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:i?.botToken,fetch:n?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let a=r.chatId??``;if(a)try{await sendTelegramChatAction({action:e,apiBaseUrl:n?.apiBaseUrl,chatId:a,credentials:i,fetch:n?.fetch,messageThreadId:r.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:a})}}}}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromMessage(e.message,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=prependTelegramContext(buildTelegramTurnMessage(e.message,i),{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}),o=e.message.text||e.message.caption,s=e.message.replyToMessage?.from?.isBot===!0&&o.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:o})]:void 0;try{await e.send({inputResponses:s,message:a,context:r.context},{auth:r.auth,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromCallbackQuery(e.query,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.send({inputResponses:[telegramCallbackInputResponse(e.query.data)]},{auth:null,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function stateFromMessage(e,t){let n=e.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:e.chat.id,chatType:e.chat.type,conversationId:n?null:conversationIdForMessage(e),messageThreadId:e.messageThreadId??null,triggeringUserId:e.from?.id??null}}function stateFromCallbackQuery(e,t){let n=e.message;if(!n)return{...initialTelegramState(t.botUsername),triggeringUserId:e.from.id};let r=n.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:n.chat.id,chatType:n.chat.type,conversationId:r?null:n.messageId,messageThreadId:n.messageThreadId??null,triggeringUserId:e.from.id}}function conversationIdForMessage(e){return e.replyToMessage?.from?.isBot===!0?e.replyToMessage.messageId:e.messageId}function continuationTokenFromState(e){return telegramContinuationToken({chatId:e.chatId??``,conversationId:e.chatType===`private`?void 0:e.conversationId??void 0,messageThreadId:e.messageThreadId??void 0})}function initialTelegramState(e){return{botUsername:e??null,chatId:null,chatType:null,conversationId:null,hitlCallbacks:{},messageThreadId:null,nextHitlCallbackId:0,pendingFreeformReplies:{},triggeringUserId:null}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}function readOptionalString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{parseJsonObject}from"#shared/json.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{formatTelegramContextBlock,parseTelegramUpdate}from"#public/channels/telegram/inbound.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";const log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=e.onMessage??defaultOnMessage,r={...defaultEvents,...e.events},c=defineChannel({kindHint:`telegram`,state:initialTelegramState(e.botUsername),fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/ash/v1/telegram`,async(r,{send:a,waitUntil:o})=>{let s=await verifyInbound(r,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:n,send:a,uploadPolicy:t})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,send:a})),new Response(`ok`))})],async receive(t,{send:n}){let r=t.args,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires args.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readOptionalString(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=o;return s!==void 0&&(c=(await buildTelegramHandle({config:e,state:{...initialTelegramState(e.botUsername),chatId:i,messageThreadId:a??null}}).sendMessage(s)).id||void 0),n(t.message,{auth:t.auth,continuationToken:telegramContinuationToken({chatId:i,conversationId:c,messageThreadId:a}),state:{...initialTelegramState(e.botUsername),chatId:i,conversationId:c??null,messageThreadId:a??null}})},events:r});return attachTelegramDeliver(c),c}function rebuildTelegramContext(e,t,n){return{state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let n=e.config.api,r=e.state,i=e.config.credentials;function anchor(t){!t.id||r.chatType===`private`||(r.conversationId=t.id,r.chatId&&e.session?.setContinuationToken(telegramContinuationToken({chatId:r.chatId,conversationId:t.id,messageThreadId:r.messageThreadId??void 0})))}async function sendOne(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for outbound message.`);let a=await sendTelegramMessage({apiBaseUrl:n?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??r.messageThreadId??void 0},credentials:i,fetch:n?.fetch,fileBaseUrl:n?.fileBaseUrl,chatId:t});return anchor(a),a}return{botUsername:r.botUsername??e.config.botUsername,chatId:r.chatId??``,chatType:r.chatType??void 0,conversationId:r.conversationId??void 0,messageThreadId:r.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:n?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:i,fetch:n?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let t=r.chatId??``;if(!t)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:n?.apiBaseUrl,chatId:t,credentials:i,fetch:n?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},post(e){return postTelegramMessage(e,sendOne)},request(e,t){return callTelegramApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:i?.botToken,fetch:n?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let a=r.chatId??``;if(a)try{await sendTelegramChatAction({action:e,apiBaseUrl:n?.apiBaseUrl,chatId:a,credentials:i,fetch:n?.fetch,messageThreadId:r.messageThreadId??void 0})}catch(e){logError(log,`Telegram typing indicator failed — swallowed`,e,{chatId:a})}}}}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromMessage(e.message,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=buildTelegramTurnMessage(e.message,i),o=formatTelegramContextBlock({botUsername:e.config.botUsername,chatId:e.message.chat.id,chatTitle:e.message.chat.title,chatType:e.message.chat.type,messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),s=r.context??[],c=e.message.text||e.message.caption,l=e.message.replyToMessage?.from?.isBot===!0&&c.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:c})]:void 0;try{await e.send({inputResponses:l,message:a,context:[o,...s]},{auth:r.auth,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromCallbackQuery(e.query,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.send({inputResponses:[telegramCallbackInputResponse(e.query.data)]},{auth:null,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let t=e.adapter;t.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,context:e.context}:e.message===void 0?void 0:{message:e.message,context:e.context}}return defaultDeliverResult(e)}}function stateFromMessage(e,t){let n=e.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:e.chat.id,chatType:e.chat.type,conversationId:n?null:conversationIdForMessage(e),messageThreadId:e.messageThreadId??null,triggeringUserId:e.from?.id??null}}function stateFromCallbackQuery(e,t){let n=e.message;if(!n)return{...initialTelegramState(t.botUsername),triggeringUserId:e.from.id};let r=n.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:n.chat.id,chatType:n.chat.type,conversationId:r?null:n.messageId,messageThreadId:n.messageThreadId??null,triggeringUserId:e.from.id}}function conversationIdForMessage(e){return e.replyToMessage?.from?.isBot===!0?e.replyToMessage.messageId:e.messageId}function continuationTokenFromState(e){return telegramContinuationToken({chatId:e.chatId??``,conversationId:e.chatType===`private`?void 0:e.conversationId??void 0,messageThreadId:e.messageThreadId??void 0})}function initialTelegramState(e){return{botUsername:e??null,chatId:null,chatType:null,conversationId:null,hitlCallbacks:{},messageThreadId:null,nextHitlCallbackId:0,pendingFreeformReplies:{},triggeringUserId:null}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}function readOptionalString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
@@ -4,7 +4,6 @@
4
4
  * The channel owns these small data shapes instead of exposing raw
5
5
  * Twilio webhook payloads as the public API surface.
6
6
  */
7
- import type { UserContent } from "ai";
8
7
  /** Channel-owned representation of one inbound Twilio text message. */
9
8
  export interface TwilioTextMessage {
10
9
  readonly from: string;
@@ -55,5 +54,3 @@ export declare function parseTwilioVoiceCall(params: URLSearchParams): TwilioVoi
55
54
  export declare function parseTwilioVoiceTranscription(params: URLSearchParams): TwilioVoiceTranscription | null;
56
55
  /** Renders a deterministic `<twilio_context>` block for the model. */
57
56
  export declare function formatTwilioContextBlock(context: TwilioInboundContext): string;
58
- /** Prepends a `<twilio_context>` block to the inbound turn message. */
59
- export declare function prependTwilioContext(message: string | UserContent, context: TwilioInboundContext): string | UserContent;
@@ -1,2 +1,2 @@
1
1
  function parseTwilioTextMessage(e){let t=requiredParam(e,`From`),n=requiredParam(e,`Body`);return!t||!n?null:{accountSid:optionalParam(e,`AccountSid`),body:n,from:t,messageSid:optionalParam(e,`MessageSid`)??optionalParam(e,`SmsMessageSid`),raw:e,to:optionalParam(e,`To`)}}function parseTwilioVoiceCall(e){let t=requiredParam(e,`From`)??requiredParam(e,`Caller`);return t?{accountSid:optionalParam(e,`AccountSid`),callSid:optionalParam(e,`CallSid`),from:t,raw:e,to:optionalParam(e,`To`)??optionalParam(e,`Called`)}:null}function parseTwilioVoiceTranscription(e){let t=requiredParam(e,`From`)??requiredParam(e,`Caller`);if(!t)return null;let n=parseTranscriptionData(optionalParam(e,`TranscriptionData`));if(optionalParam(e,`Final`)===`false`)return null;let r=optionalParam(e,`SpeechResult`)??optionalParam(e,`TranscriptionText`)??n?.transcript??``;return r.trim()?{callSid:optionalParam(e,`CallSid`),confidence:parseConfidence(optionalParam(e,`Confidence`)??n?.confidence),from:t,raw:e,text:r,to:optionalParam(e,`To`)??optionalParam(e,`Called`),transcriptionSid:optionalParam(e,`TranscriptionSid`)}:null}function formatTwilioContextBlock(e){return[`<twilio_context>`,`channel: ${e.channel}`,`response_medium: sms`,`response_instructions: Reply for SMS in plain text. Keep the response concise and avoid Markdown formatting, tables, headings, code fences, and long lists. Ask at most one short follow-up question when more information is needed.`,`from: ${e.from}`,...e.to?[`to: ${e.to}`]:[],...e.messageSid?[`message_sid: ${e.messageSid}`]:[],...e.callSid?[`call_sid: ${e.callSid}`]:[],`</twilio_context>`].join(`
2
- `)}function prependTwilioContext(e,t){let n=formatTwilioContextBlock(t);return typeof e==`string`?e.length>0?`${n}\n\n${e}`:n:[{type:`text`,text:n},...e]}function requiredParam(e,t){let n=e.get(t);return n&&n.trim().length>0?n:null}function optionalParam(e,t){let n=e.get(t);return n===null||n.length===0?void 0:n}function parseTranscriptionData(e){if(!e)return null;try{let t=JSON.parse(e);return{confidence:typeof t.confidence==`number`||typeof t.confidence==`string`?String(t.confidence):void 0,transcript:typeof t.transcript==`string`?t.transcript:void 0}}catch{return null}}function parseConfidence(e){if(e===void 0)return;let t=Number(e);return Number.isFinite(t)?t:void 0}export{formatTwilioContextBlock,parseTwilioTextMessage,parseTwilioVoiceCall,parseTwilioVoiceTranscription,prependTwilioContext};
2
+ `)}function requiredParam(e,t){let n=e.get(t);return n&&n.trim().length>0?n:null}function optionalParam(e,t){let n=e.get(t);return n===null||n.length===0?void 0:n}function parseTranscriptionData(e){if(!e)return null;try{let t=JSON.parse(e);return{confidence:typeof t.confidence==`number`||typeof t.confidence==`string`?String(t.confidence):void 0,transcript:typeof t.transcript==`string`?t.transcript:void 0}}catch{return null}}function parseConfidence(e){if(e===void 0)return;let t=Number(e);return Number.isFinite(t)?t:void 0}export{formatTwilioContextBlock,parseTwilioTextMessage,parseTwilioVoiceCall,parseTwilioVoiceTranscription};
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{verifyTwilioRequest}from"#public/channels/twilio/verify.js";import{callTwilioApi,sendTwilioMessage,twilioContinuationToken,updateTwilioCall}from"#public/channels/twilio/api.js";import{emptyTwilioResponse,gatherSpeechTwilioResponse,sayTwilioResponse}from"#public/channels/twilio/twiml.js";import{defaultEvents,defaultOnText,defaultOnVoice,defaultOnVoiceTranscription}from"#public/channels/twilio/defaults.js";import{parseTwilioTextMessage,parseTwilioVoiceCall,parseTwilioVoiceTranscription,prependTwilioContext}from"#public/channels/twilio/inbound.js";const log=createLogger(`twilio.channel`);function twilioChannel(e){assertAllowFromConfigured(e);let r=buildRoutes(e.route??`/ash/v1/twilio`),i=e.onText??defaultOnText,a=e.onVoice??defaultOnVoice,s=e.onVoiceTranscription??defaultOnVoiceTranscription,l={...defaultEvents,...e.events};return defineChannel({kindHint:`twilio`,state:{from:null,to:null,lastCallSid:null,lastMessageSid:null},metadata(e){return{from:e.from,lastCallSid:e.lastCallSid??null,lastMessageSid:e.lastMessageSid??null,to:e.to}},context(t,n){return rebuildTwilioContext(t,n,e)},routes:[POST(r.messages,async(t,{send:n,waitUntil:r})=>{let a=await verifyInbound(t,e);if(a===null)return new Response(`unauthorized`,{status:401});let o=parseTwilioTextMessage(a.params);return o?await isAllowed(o.from,e.allowFrom)?(r(dispatchText({config:e,message:o,onText:i,send:n})),emptyTwilioResponse()):new Response(`forbidden`,{status:403}):emptyTwilioResponse()}),POST(r.voice,async t=>{let n=await verifyInbound(t,e);if(n===null)return new Response(`unauthorized`,{status:401});let i=parseTwilioVoiceCall(n.params);if(!i)return sayTwilioResponse(`Missing caller information.`);if(!await isAllowed(i.from,e.allowFrom))return new Response(`forbidden`,{status:403});let o=await acceptVoiceCall({call:i,config:e,onVoice:a});if(o===null)return new Response(`forbidden`,{status:403});let s=o??{};return gatherSpeechTwilioResponse({actionUrl:await buildActionUrl(t,e,r.transcription),hints:s.hints??e.voice?.hints,language:s.language??e.voice?.language,profanityFilter:s.profanityFilter??e.voice?.profanityFilter,prompt:s.prompt??e.voice?.prompt??`Please say your message after the tone.`,speechModel:s.speechModel??e.voice?.speechModel,speechTimeout:s.speechTimeout??e.voice?.speechTimeout??`auto`,timeoutSeconds:s.timeoutSeconds??e.voice?.timeoutSeconds,voice:s.voice??e.voice?.voice})}),POST(r.transcription,async(t,{send:n,waitUntil:i})=>{let a=await verifyInbound(t,e);if(a===null)return new Response(`unauthorized`,{status:401});let o=parseTwilioVoiceTranscription(a.params);return o?await isAllowed(o.from,e.allowFrom)?(i(dispatchVoiceTranscription({config:e,onVoiceTranscription:s,send:n,transcription:o})),sayTwilioResponse(e.voice?.acknowledgement??`Thanks. I'll follow up by text.`)):new Response(`forbidden`,{status:403}):gatherSpeechTwilioResponse({actionUrl:await buildActionUrl(t,e,r.transcription),language:e.voice?.language,prompt:e.voice?.prompt??`Please say your message after the tone.`,speechTimeout:e.voice?.speechTimeout??`auto`,timeoutSeconds:e.voice?.timeoutSeconds})})],async receive(t,{send:n}){let r=readString(t.args.phoneNumber);if(!r)throw Error(`twilioChannel().receive requires args.phoneNumber.`);let i=readString(t.args.from)??e.messaging?.from??null;return n(t.message,{auth:t.auth,continuationToken:twilioContinuationToken(r,i??void 0),state:{from:r,lastCallSid:null,lastMessageSid:null,to:i}})},events:l})}function rebuildTwilioContext(e,t,n){return{state:e,twilio:buildTwilioHandle({callSid:e.lastCallSid??void 0,config:n,from:e.from??``,to:e.to??void 0})}}function buildTwilioHandle(e){let t=e.config.api,n=e.config.credentials,r=e.config.messaging?.from??e.to,o=e.config.messaging?.messagingServiceSid,c=e.config.messaging?.statusCallbackUrl;return{callSid:e.callSid,from:e.from,to:e.to,request(e,r){return callTwilioApi({apiBaseUrl:t?.apiBaseUrl,body:r,credentials:n,fetch:t?.fetch,path:e})},sendMessage(i,s){return sendTwilioMessage({apiBaseUrl:t?.apiBaseUrl,body:i,credentials:n,fetch:t?.fetch,from:s?.from??r,messagingServiceSid:s?.messagingServiceSid??o,statusCallbackUrl:s?.statusCallbackUrl??c,to:s?.to??e.from})},updateCall(e,r){return updateTwilioCall({apiBaseUrl:t?.apiBaseUrl,callSid:e,credentials:n,fetch:t?.fetch,twiml:r})}}}function buildRoutes(e){let t=e.endsWith(`/`)?e.slice(0,-1):e;return{messages:`${t}/messages`,transcription:`${t}/voice/transcription`,voice:`${t}/voice`}}function assertAllowFromConfigured(e){if(e?.allowFrom===void 0)throw Error(`twilioChannel requires allowFrom. Use allowFrom: "*" to allow all numbers.`)}async function verifyInbound(e,t){try{return await verifyTwilioRequest(e,{authToken:t.credentials?.authToken,webhookUrl:t.webhookUrl})}catch(e){return log.warn(`twilio inbound verification failed`,{error:e}),null}}async function dispatchText(e){let{message:t}=e,n={twilio:buildTwilioHandle({callSid:void 0,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onText(n,t)}catch(e){log.error(`text handler failed`,{error:e});return}if(r==null)return;let i=prependTwilioContext(t.body,{channel:`text`,from:t.from,messageSid:t.messageSid,to:t.to});try{await e.send(i,{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:null,lastMessageSid:t.messageSid??null,to:t.to??null}})}catch(e){log.error(`text delivery failed`,{error:e})}}async function acceptVoiceCall(e){let{call:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})};try{return await e.onVoice(n,t)}catch(e){return log.error(`voice handler failed`,{error:e}),null}}async function dispatchVoiceTranscription(e){let{transcription:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onVoiceTranscription(n,t)}catch(e){log.error(`voice transcription handler failed`,{error:e});return}if(r==null)return;let i=prependTwilioContext(t.text,{callSid:t.callSid,channel:`voice`,from:t.from,to:t.to});try{await e.send(i,{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:t.callSid??null,lastMessageSid:null,to:t.to??null}})}catch(e){log.error(`voice transcription delivery failed`,{error:e})}}async function isAllowed(e,t){let n=typeof t==`function`?await t():t;return n===`*`?!0:typeof n==`string`?n===e:n.includes(e)}async function buildActionUrl(e,t,n){let r=typeof t.publicBaseUrl==`function`?await t.publicBaseUrl(e):t.publicBaseUrl;if(r)return new URL(n,ensureTrailingSlash(r)).toString();let i=new URL(e.url);return i.pathname=n,i.search=``,i.toString()}function ensureTrailingSlash(e){return e.endsWith(`/`)?e:`${e}/`}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{twilioChannel};
1
+ import{createLogger}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{verifyTwilioRequest}from"#public/channels/twilio/verify.js";import{callTwilioApi,sendTwilioMessage,twilioContinuationToken,updateTwilioCall}from"#public/channels/twilio/api.js";import{emptyTwilioResponse,gatherSpeechTwilioResponse,sayTwilioResponse}from"#public/channels/twilio/twiml.js";import{defaultEvents,defaultOnText,defaultOnVoice,defaultOnVoiceTranscription}from"#public/channels/twilio/defaults.js";import{formatTwilioContextBlock,parseTwilioTextMessage,parseTwilioVoiceCall,parseTwilioVoiceTranscription}from"#public/channels/twilio/inbound.js";const log=createLogger(`twilio.channel`);function twilioChannel(e){assertAllowFromConfigured(e);let r=buildRoutes(e.route??`/ash/v1/twilio`),i=e.onText??defaultOnText,a=e.onVoice??defaultOnVoice,s=e.onVoiceTranscription??defaultOnVoiceTranscription,l={...defaultEvents,...e.events};return defineChannel({kindHint:`twilio`,state:{from:null,to:null,lastCallSid:null,lastMessageSid:null},metadata(e){return{from:e.from,lastCallSid:e.lastCallSid??null,lastMessageSid:e.lastMessageSid??null,to:e.to}},context(t,n){return rebuildTwilioContext(t,n,e)},routes:[POST(r.messages,async(t,{send:n,waitUntil:r})=>{let a=await verifyInbound(t,e);if(a===null)return new Response(`unauthorized`,{status:401});let o=parseTwilioTextMessage(a.params);return o?await isAllowed(o.from,e.allowFrom)?(r(dispatchText({config:e,message:o,onText:i,send:n})),emptyTwilioResponse()):new Response(`forbidden`,{status:403}):emptyTwilioResponse()}),POST(r.voice,async t=>{let n=await verifyInbound(t,e);if(n===null)return new Response(`unauthorized`,{status:401});let i=parseTwilioVoiceCall(n.params);if(!i)return sayTwilioResponse(`Missing caller information.`);if(!await isAllowed(i.from,e.allowFrom))return new Response(`forbidden`,{status:403});let o=await acceptVoiceCall({call:i,config:e,onVoice:a});if(o===null)return new Response(`forbidden`,{status:403});let s=o??{};return gatherSpeechTwilioResponse({actionUrl:await buildActionUrl(t,e,r.transcription),hints:s.hints??e.voice?.hints,language:s.language??e.voice?.language,profanityFilter:s.profanityFilter??e.voice?.profanityFilter,prompt:s.prompt??e.voice?.prompt??`Please say your message after the tone.`,speechModel:s.speechModel??e.voice?.speechModel,speechTimeout:s.speechTimeout??e.voice?.speechTimeout??`auto`,timeoutSeconds:s.timeoutSeconds??e.voice?.timeoutSeconds,voice:s.voice??e.voice?.voice})}),POST(r.transcription,async(t,{send:n,waitUntil:i})=>{let a=await verifyInbound(t,e);if(a===null)return new Response(`unauthorized`,{status:401});let o=parseTwilioVoiceTranscription(a.params);return o?await isAllowed(o.from,e.allowFrom)?(i(dispatchVoiceTranscription({config:e,onVoiceTranscription:s,send:n,transcription:o})),sayTwilioResponse(e.voice?.acknowledgement??`Thanks. I'll follow up by text.`)):new Response(`forbidden`,{status:403}):gatherSpeechTwilioResponse({actionUrl:await buildActionUrl(t,e,r.transcription),language:e.voice?.language,prompt:e.voice?.prompt??`Please say your message after the tone.`,speechTimeout:e.voice?.speechTimeout??`auto`,timeoutSeconds:e.voice?.timeoutSeconds})})],async receive(t,{send:n}){let r=readString(t.args.phoneNumber);if(!r)throw Error(`twilioChannel().receive requires args.phoneNumber.`);let i=readString(t.args.from)??e.messaging?.from??null;return n(t.message,{auth:t.auth,continuationToken:twilioContinuationToken(r,i??void 0),state:{from:r,lastCallSid:null,lastMessageSid:null,to:i}})},events:l})}function rebuildTwilioContext(e,t,n){return{state:e,twilio:buildTwilioHandle({callSid:e.lastCallSid??void 0,config:n,from:e.from??``,to:e.to??void 0})}}function buildTwilioHandle(e){let t=e.config.api,n=e.config.credentials,r=e.config.messaging?.from??e.to,o=e.config.messaging?.messagingServiceSid,c=e.config.messaging?.statusCallbackUrl;return{callSid:e.callSid,from:e.from,to:e.to,request(e,r){return callTwilioApi({apiBaseUrl:t?.apiBaseUrl,body:r,credentials:n,fetch:t?.fetch,path:e})},sendMessage(i,s){return sendTwilioMessage({apiBaseUrl:t?.apiBaseUrl,body:i,credentials:n,fetch:t?.fetch,from:s?.from??r,messagingServiceSid:s?.messagingServiceSid??o,statusCallbackUrl:s?.statusCallbackUrl??c,to:s?.to??e.from})},updateCall(e,r){return updateTwilioCall({apiBaseUrl:t?.apiBaseUrl,callSid:e,credentials:n,fetch:t?.fetch,twiml:r})}}}function buildRoutes(e){let t=e.endsWith(`/`)?e.slice(0,-1):e;return{messages:`${t}/messages`,transcription:`${t}/voice/transcription`,voice:`${t}/voice`}}function assertAllowFromConfigured(e){if(e?.allowFrom===void 0)throw Error(`twilioChannel requires allowFrom. Use allowFrom: "*" to allow all numbers.`)}async function verifyInbound(e,t){try{return await verifyTwilioRequest(e,{authToken:t.credentials?.authToken,webhookUrl:t.webhookUrl})}catch(e){return log.warn(`twilio inbound verification failed`,{error:e}),null}}async function dispatchText(e){let{message:t}=e,n={twilio:buildTwilioHandle({callSid:void 0,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onText(n,t)}catch(e){log.error(`text handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({channel:`text`,from:t.from,messageSid:t.messageSid,to:t.to});try{await e.send({message:t.body,context:[i]},{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:null,lastMessageSid:t.messageSid??null,to:t.to??null}})}catch(e){log.error(`text delivery failed`,{error:e})}}async function acceptVoiceCall(e){let{call:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})};try{return await e.onVoice(n,t)}catch(e){return log.error(`voice handler failed`,{error:e}),null}}async function dispatchVoiceTranscription(e){let{transcription:t}=e,n={twilio:buildTwilioHandle({callSid:t.callSid,config:e.config,from:t.from,to:t.to})},r;try{r=await e.onVoiceTranscription(n,t)}catch(e){log.error(`voice transcription handler failed`,{error:e});return}if(r==null)return;let i=formatTwilioContextBlock({callSid:t.callSid,channel:`voice`,from:t.from,to:t.to});try{await e.send({message:t.text,context:[i]},{auth:r.auth,continuationToken:twilioContinuationToken(t.from,t.to),state:{from:t.from,lastCallSid:t.callSid??null,lastMessageSid:null,to:t.to??null}})}catch(e){log.error(`voice transcription delivery failed`,{error:e})}}async function isAllowed(e,t){let n=typeof t==`function`?await t():t;return n===`*`?!0:typeof n==`string`?n===e:n.includes(e)}async function buildActionUrl(e,t,n){let r=typeof t.publicBaseUrl==`function`?await t.publicBaseUrl(e):t.publicBaseUrl;if(r)return new URL(n,ensureTrailingSlash(r)).toString();let i=new URL(e.url);return i.pathname=n,i.search=``,i.toString()}function ensureTrailingSlash(e){return e.endsWith(`/`)?e:`${e}/`}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{twilioChannel};
@@ -17,28 +17,12 @@ export interface HookContext extends SessionContext {
17
17
  readonly continuationToken?: string;
18
18
  };
19
19
  }
20
- /**
21
- * Lifecycle hook signature shared by `lifecycle.session` and
22
- * `lifecycle.turn`.
23
- *
24
- * Hooks are side-effect-only — they cannot inject model context.
25
- * To contribute runtime model messages, use `defineDynamic` +
26
- * `defineInstructions` in `agent/instructions/`.
27
- */
28
- export type LifecycleHook = (ctx: HookContext) => void | Promise<void>;
29
20
  /**
30
21
  * Side-effect-only handler for one accepted runtime stream event.
31
22
  *
32
23
  * The typed event is the first argument, `ctx` is the last.
33
24
  */
34
25
  export type StreamEventHook<TEvent> = (event: TEvent, ctx: HookContext) => void | Promise<void>;
35
- /**
36
- * Per-stage lifecycle hooks an authored hook file may declare.
37
- */
38
- export interface LifecycleHooks {
39
- readonly session?: LifecycleHook;
40
- readonly turn?: LifecycleHook;
41
- }
42
26
  /**
43
27
  * Map of stream-event subscribers an authored hook file may declare.
44
28
  *
@@ -55,18 +39,18 @@ export type StreamEventHooks = {
55
39
  /**
56
40
  * Public hook definition authored in `agent/hooks/*.ts`.
57
41
  *
58
- * One file may declare lifecycle subscribers (under `lifecycle:`),
59
- * stream-event subscribers (under `events:`), or both. The structural
60
- * split makes the contract explicit: `lifecycle:` can mutate the next
61
- * model call's context; `events:` is observe-only.
42
+ * Hook files declare stream-event subscribers (under `events:`) that
43
+ * fire after Ash has accepted and durably recorded each event.
44
+ * Handlers are observe-only they cannot inject model context. To
45
+ * contribute runtime model messages, use `defineDynamic` +
46
+ * `defineInstructions` in `agent/instructions/`.
62
47
  */
63
48
  export interface HookDefinition {
64
- readonly lifecycle?: LifecycleHooks;
65
49
  readonly events?: StreamEventHooks;
66
50
  }
67
51
  /**
68
52
  * Identity-with-types helper. Authors export
69
- * `defineHook({ lifecycle: { session, turn }, events: { "session.started": … } })`
53
+ * `defineHook({ events: { "session.started": … } })`
70
54
  * and receive a typed {@link HookDefinition}.
71
55
  */
72
56
  export declare function defineHook<T extends HookDefinition>(definition: ExactDefinition<T, HookDefinition>): T;
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Hook authoring helpers for `agent/hooks/*.ts` files.
3
3
  *
4
- * Hooks subscribe to lifecycle moments (under `lifecycle:`) and runtime
5
- * stream events (under `events:`). See {@link defineHook} for the
6
- * authoring shape and {@link HookContext} for the runtime context every
7
- * handler receives.
4
+ * Hooks subscribe to runtime stream events (under `events:`).
5
+ * See {@link defineHook} for the authoring shape and
6
+ * {@link HookContext} for the runtime context every handler receives.
8
7
  */
9
- export { type HookContext, type HookDefinition, type LifecycleHook, type LifecycleHooks, type StreamEventHook, type StreamEventHooks, defineHook, } from "../definitions/hook.js";
8
+ export { type HookContext, type HookDefinition, type StreamEventHook, type StreamEventHooks, defineHook, } from "../definitions/hook.js";
@@ -21,9 +21,8 @@ export interface ResolvedRuntimeAgentNode {
21
21
  */
22
22
  readonly channels: readonly ResolvedChannelDefinition[];
23
23
  /**
24
- * Per-node hook registry. Lifecycle subscribers run inside `runStep`
25
- * around the harness callback; stream-event subscribers fan out
26
- * alongside channel adapter event handlers.
24
+ * Per-node hook registry. Stream-event subscribers fan out alongside
25
+ * channel adapter event handlers.
27
26
  */
28
27
  readonly hookRegistry: RuntimeHookRegistry;
29
28
  readonly nodeId: string;
@@ -1,13 +1,5 @@
1
- import type { LifecycleHook, StreamEventHook } from "../../public/definitions/hook.js";
1
+ import type { StreamEventHook } from "../../public/definitions/hook.js";
2
2
  import type { ResolvedHookDefinition } from "../types.js";
3
- /**
4
- * One ordered lifecycle subscriber paired with the slug of its source.
5
- * The slug is used for diagnostics.
6
- */
7
- interface RuntimeLifecycleHookEntry {
8
- readonly slug: string;
9
- readonly handler: LifecycleHook;
10
- }
11
3
  /**
12
4
  * One ordered stream-event subscriber paired with its source slug.
13
5
  *
@@ -20,19 +12,12 @@ interface RuntimeStreamEventHookEntry {
20
12
  readonly eventType: string;
21
13
  }
22
14
  /**
23
- * Per-node runtime hook registry. Lifecycle subscribers are stored as
24
- * flat ordered arrays registry order is framework defaults (none ship
25
- * in v1) plugin install order (deferred) agent-local lexicographic
26
- * on full slug. Within a single file, object-key declaration order is
27
- * preserved.
28
- *
29
- * Stream-event subscribers are split into typed buckets (keyed by event
30
- * type) and a flat wildcard bucket so dispatch can iterate one typed
31
- * bucket plus the wildcard bucket without scanning every entry.
15
+ * Per-node runtime hook registry. Stream-event subscribers are split
16
+ * into typed buckets (keyed by event type) and a flat wildcard bucket
17
+ * so dispatch can iterate one typed bucket plus the wildcard bucket
18
+ * without scanning every entry.
32
19
  */
33
20
  export interface RuntimeHookRegistry {
34
- readonly session: readonly RuntimeLifecycleHookEntry[];
35
- readonly turn: readonly RuntimeLifecycleHookEntry[];
36
21
  readonly streamEventsByType: ReadonlyMap<string, readonly RuntimeStreamEventHookEntry[]>;
37
22
  readonly streamEventsWildcard: readonly RuntimeStreamEventHookEntry[];
38
23
  }
@@ -1 +1 @@
1
- function createEmptyHookRegistry(){return{session:[],turn:[],streamEventsByType:new Map,streamEventsWildcard:[]}}function createRuntimeHookRegistry(e){let t=[],n=[],r=new Map,i=[];for(let a of e){let e=a.lifecycle.session;e!==void 0&&t.push({slug:a.slug,handler:e});let o=a.lifecycle.turn;o!==void 0&&n.push({slug:a.slug,handler:o});for(let[e,t]of Object.entries(a.events)){let n={slug:a.slug,handler:t,eventType:e};if(e===`*`)i.push(n);else{let t=r.get(e)??[];t.push(n),r.set(e,t)}}}return{session:t,turn:n,streamEventsByType:r,streamEventsWildcard:i}}export{createEmptyHookRegistry,createRuntimeHookRegistry};
1
+ function createEmptyHookRegistry(){return{streamEventsByType:new Map,streamEventsWildcard:[]}}function createRuntimeHookRegistry(e){let t=new Map,n=[];for(let r of e)for(let[e,i]of Object.entries(r.events)){let a={slug:r.slug,handler:i,eventType:e};if(e===`*`)n.push(a);else{let n=t.get(e)??[];n.push(a),t.set(e,n)}}return{streamEventsByType:t,streamEventsWildcard:n}}export{createEmptyHookRegistry,createRuntimeHookRegistry};
@@ -5,9 +5,9 @@ import type { ResolvedHookDefinition } from "./types.js";
5
5
  * Resolves one compiled authored hook into a runtime-owned definition
6
6
  * with live handlers reattached from the authored module.
7
7
  *
8
- * The authored shape is `{ lifecycle?: { session?, turn? }, events?: { ... } }`.
8
+ * The authored shape is `{ events?: { ... } }`.
9
9
  * Each declared handler must be a function. Any other shape raises a
10
10
  * {@link ResolveAgentError} so typos surface at resolve time instead of
11
- * at first lifecycle call.
11
+ * at first dispatch call.
12
12
  */
13
13
  export declare function resolveHookDefinition(definition: CompiledHookDefinition, moduleMap: CompiledModuleMap, nodeId: string | undefined): Promise<ResolvedHookDefinition>;
@@ -1 +1 @@
1
- import{expectFunction,expectObjectRecord}from"../internal/authored-module.js";import{ResolveAgentError,loadResolvedModuleExport}from"./resolve-helpers.js";import{toErrorMessage}from"../shared/errors.js";async function resolveHookDefinition(a,o,s){try{let n=expectObjectRecord(await loadResolvedModuleExport({definition:a,kindLabel:`hook`,moduleMap:o,nodeId:s}),describe(a,`to return an object`)),i={},c={},l=n.lifecycle;if(l!==void 0){let n=expectObjectRecord(l,describe(a,"to expose `lifecycle` as an object"));for(let t of[`session`,`turn`]){let r=n[t];r!==void 0&&(i[t]=expectFunction(r,describe(a,`to provide a function for "lifecycle.${t}"`)))}}let u=n.events;if(u!==void 0){let n=expectObjectRecord(u,describe(a,"to expose `events` as an object"));for(let[t,r]of Object.entries(n))r!==void 0&&(c[t]=expectFunction(r,describe(a,`to provide a function for "events.${t}"`)))}return{events:c,exportName:a.exportName,lifecycle:i,logicalPath:a.logicalPath,slug:a.slug,sourceId:a.sourceId,sourceKind:`module`}}catch(e){throw e instanceof ResolveAgentError?e:new ResolveAgentError(`Failed to attach hook handlers from "${a.logicalPath}": ${toErrorMessage(e)}`,{logicalPath:a.logicalPath,sourceId:a.sourceId})}}function describe(e,t){return`Expected the hook export "${e.exportName??`default`}" from "${e.logicalPath}" ${t}.`}export{resolveHookDefinition};
1
+ import{expectFunction,expectObjectRecord}from"../internal/authored-module.js";import{ResolveAgentError,loadResolvedModuleExport}from"./resolve-helpers.js";import{toErrorMessage}from"../shared/errors.js";async function resolveHookDefinition(a,o,s){try{let n=expectObjectRecord(await loadResolvedModuleExport({definition:a,kindLabel:`hook`,moduleMap:o,nodeId:s}),describe(a,`to return an object`)),i={},c=n.events;if(c!==void 0){let n=expectObjectRecord(c,describe(a,"to expose `events` as an object"));for(let[t,r]of Object.entries(n))r!==void 0&&(i[t]=expectFunction(r,describe(a,`to provide a function for "events.${t}"`)))}return{events:i,exportName:a.exportName,logicalPath:a.logicalPath,slug:a.slug,sourceId:a.sourceId,sourceKind:`module`}}catch(e){throw e instanceof ResolveAgentError?e:new ResolveAgentError(`Failed to attach hook handlers from "${a.logicalPath}": ${toErrorMessage(e)}`,{logicalPath:a.logicalPath,sourceId:a.sourceId})}}function describe(e,t){return`Expected the hook export "${e.exportName??`default`}" from "${e.logicalPath}" ${t}.`}export{resolveHookDefinition};
@@ -6,7 +6,7 @@ import type { DiscoverDiagnosticsSummary } from "#discover/diagnostics.js";
6
6
  import type { ChannelMethod, RouteContext } from "#public/definitions/channel.js";
7
7
  import type { RouteHandler } from "#channel/routes.js";
8
8
  import type { OutboundAuthFn } from "#public/agents/auth.js";
9
- import type { LifecycleHooks, StreamEventHook } from "#public/definitions/hook.js";
9
+ import type { StreamEventHook } from "#public/definitions/hook.js";
10
10
  import type { CompactionInput, CompactionHookResult, NeedsApprovalContext, ToolModelOutput, ToolRetentionPolicy } from "#public/definitions/tool.js";
11
11
  import type { AuthorizationDefinition, HeadersDefinition, ToolFilterDefinition } from "#runtime/connections/types.js";
12
12
  import type { CompiledWorkspaceResourceRoot } from "#compiler/manifest.js";
@@ -148,8 +148,8 @@ export type ResolvedToolDefinition = Readonly<Optional<InternalToolDefinitionWit
148
148
  };
149
149
  /**
150
150
  * Runtime-owned authored hook definition resolved from a compiled module
151
- * map. Carries live lifecycle and stream-event handlers reattached from
152
- * the authored module's exported nested maps.
151
+ * map. Carries live stream-event handlers reattached from the authored
152
+ * module's exported nested maps.
153
153
  *
154
154
  * Per-handler validation runs at resolve time inside
155
155
  * {@link resolveHookDefinition}; missing handlers are simply absent from
@@ -160,12 +160,6 @@ export interface ResolvedHookDefinition extends ResolvedModuleSourceRef {
160
160
  * Path-relative slug used for diagnostics and ordering.
161
161
  */
162
162
  readonly slug: string;
163
- /**
164
- * Lifecycle subscribers reattached from the authored
165
- * `lifecycle: { session?, turn? }` map. Keys not present on the
166
- * authored map are absent here.
167
- */
168
- readonly lifecycle: LifecycleHooks;
169
163
  /**
170
164
  * Stream-event subscribers reattached from the authored
171
165
  * `events: { ... }` map, keyed by event type. Includes the `*`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "experimental-ash",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "bin": {
5
5
  "ash": "./bin/ash.js",
6
6
  "experimental-ash": "./bin/ash.js"