experimental-ash 0.42.0 → 0.44.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 (184) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/ash.js +1 -0
  3. package/dist/docs/internals/mechanical-invariants.md +16 -0
  4. package/dist/docs/public/README.md +8 -8
  5. package/dist/docs/public/advanced/{auth-and-route-protection.md → auth-and-route-protection.mdx} +11 -0
  6. package/dist/docs/public/advanced/context-control.md +4 -4
  7. package/dist/docs/public/advanced/{evals.md → evals.mdx} +11 -1
  8. package/dist/docs/public/advanced/{hooks.md → hooks.mdx} +28 -40
  9. package/dist/docs/public/advanced/instrumentation.md +142 -3
  10. package/dist/docs/public/advanced/project-layout.md +5 -5
  11. package/dist/docs/public/advanced/runs-and-streaming.md +8 -2
  12. package/dist/docs/public/advanced/session-context.md +1 -1
  13. package/dist/docs/public/advanced/typescript-api.md +50 -7
  14. package/dist/docs/public/advanced/vercel-deployment.md +1 -1
  15. package/dist/docs/public/agent-ts.md +5 -5
  16. package/dist/docs/public/channels/{discord.md → discord.mdx} +11 -0
  17. package/dist/docs/public/channels/index.md +10 -10
  18. package/dist/docs/public/channels/{slack.md → slack.mdx} +11 -0
  19. package/dist/docs/public/channels/{teams.md → teams.mdx} +12 -0
  20. package/dist/docs/public/channels/{telegram.md → telegram.mdx} +11 -0
  21. package/dist/docs/public/channels/{twilio.md → twilio.mdx} +11 -0
  22. package/dist/docs/public/{connections.md → connections.mdx} +18 -6
  23. package/dist/docs/public/frontend/README.md +16 -0
  24. package/dist/docs/public/frontend/meta.json +3 -0
  25. package/dist/docs/public/frontend/nextjs.md +192 -0
  26. package/dist/docs/public/frontend/use-ash-agent.md +332 -0
  27. package/dist/docs/public/{getting-started.md → getting-started.mdx} +12 -1
  28. package/dist/docs/public/{human-in-the-loop.md → human-in-the-loop.mdx} +12 -1
  29. package/dist/docs/public/meta.json +1 -0
  30. package/dist/docs/public/sandbox.md +39 -1
  31. package/dist/docs/public/{schedules.md → schedules.mdx} +9 -0
  32. package/dist/docs/public/skills.md +2 -2
  33. package/dist/docs/public/{subagents.md → subagents.mdx} +10 -0
  34. package/dist/docs/public/{tools.md → tools.mdx} +41 -26
  35. package/dist/src/channel/adapter.d.ts +13 -0
  36. package/dist/src/channel/compiled-channel.d.ts +4 -1
  37. package/dist/src/channel/compiled-channel.js +1 -1
  38. package/dist/src/channel/instrumentation.d.ts +10 -0
  39. package/dist/src/channel/instrumentation.js +1 -0
  40. package/dist/src/channel/routes.d.ts +8 -10
  41. package/dist/src/channel/send.js +1 -1
  42. package/dist/src/channel/types.d.ts +16 -0
  43. package/dist/src/cli/commands/channels.d.ts +2 -1
  44. package/dist/src/cli/commands/channels.js +1 -1
  45. package/dist/src/compiled/.vendor-stamp.json +1 -1
  46. package/dist/src/compiled/@vercel/sandbox/index.d.ts +12 -19
  47. package/dist/src/compiled/@vercel/sandbox/network-policy.d.ts +161 -0
  48. package/dist/src/compiled/just-bash/index.d.ts +15 -2
  49. package/dist/src/compiled/just-bash/network/types.d.ts +155 -0
  50. package/dist/src/compiler/artifacts.d.ts +1 -0
  51. package/dist/src/compiler/artifacts.js +1 -1
  52. package/dist/src/compiler/channel-instrumentation-types.d.ts +8 -0
  53. package/dist/src/compiler/channel-instrumentation-types.js +2 -0
  54. package/dist/src/compiler/manifest.d.ts +13 -1
  55. package/dist/src/compiler/manifest.js +1 -1
  56. package/dist/src/compiler/module-map.js +1 -1
  57. package/dist/src/compiler/normalize-manifest.js +1 -1
  58. package/dist/src/compiler/normalize-skill.d.ts +15 -2
  59. package/dist/src/compiler/normalize-skill.js +1 -1
  60. package/dist/src/compiler/normalize-tool.js +1 -1
  61. package/dist/src/context/dynamic-skill-lifecycle.d.ts +23 -0
  62. package/dist/src/context/dynamic-skill-lifecycle.js +1 -0
  63. package/dist/src/context/dynamic-tool-lifecycle.d.ts +2 -0
  64. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  65. package/dist/src/context/hook-lifecycle.d.ts +4 -6
  66. package/dist/src/context/hook-lifecycle.js +1 -1
  67. package/dist/src/context/keys.d.ts +6 -4
  68. package/dist/src/context/keys.js +1 -1
  69. package/dist/src/context/providers/connection.d.ts +9 -0
  70. package/dist/src/context/providers/connection.js +1 -1
  71. package/dist/src/context/providers/sandbox.js +1 -1
  72. package/dist/src/execution/ash-workflow-attributes.d.ts +118 -0
  73. package/dist/src/execution/ash-workflow-attributes.js +1 -0
  74. package/dist/src/execution/channel-context.d.ts +5 -0
  75. package/dist/src/execution/channel-context.js +1 -0
  76. package/dist/src/execution/create-session-step.d.ts +28 -1
  77. package/dist/src/execution/create-session-step.js +1 -1
  78. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  79. package/dist/src/execution/durable-session-store.d.ts +7 -0
  80. package/dist/src/execution/runtime-context.js +1 -1
  81. package/dist/src/execution/sandbox/bindings/local.js +1 -1
  82. package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
  83. package/dist/src/execution/sandbox/prewarm.js +1 -1
  84. package/dist/src/execution/sandbox/session.d.ts +6 -1
  85. package/dist/src/execution/sandbox/session.js +1 -1
  86. package/dist/src/execution/session.d.ts +6 -0
  87. package/dist/src/execution/session.js +2 -2
  88. package/dist/src/execution/skills/instructions.d.ts +3 -2
  89. package/dist/src/execution/subagent-tool.js +1 -1
  90. package/dist/src/execution/workflow-entry.js +1 -1
  91. package/dist/src/execution/workflow-steps.js +1 -1
  92. package/dist/src/harness/attachment-staging.js +1 -1
  93. package/dist/src/harness/code-mode.d.ts +0 -5
  94. package/dist/src/harness/code-mode.js +1 -1
  95. package/dist/src/harness/emission.d.ts +1 -1
  96. package/dist/src/harness/emission.js +1 -1
  97. package/dist/src/harness/instrumentation-config.d.ts +1 -1
  98. package/dist/src/harness/instrumentation-metadata.d.ts +23 -0
  99. package/dist/src/harness/instrumentation-metadata.js +1 -0
  100. package/dist/src/harness/otel-integration.d.ts +2 -2
  101. package/dist/src/harness/otel-integration.js +1 -1
  102. package/dist/src/harness/step-hooks.js +1 -1
  103. package/dist/src/harness/tool-loop.js +1 -1
  104. package/dist/src/harness/turn-tag-state.d.ts +50 -0
  105. package/dist/src/harness/turn-tag-state.js +1 -0
  106. package/dist/src/harness/types.d.ts +11 -2
  107. package/dist/src/internal/application/package.js +1 -1
  108. package/dist/src/internal/authored-definition/schema-backed.d.ts +0 -1
  109. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  110. package/dist/src/internal/instrumentation.d.ts +39 -0
  111. package/dist/src/internal/instrumentation.js +1 -0
  112. package/dist/src/internal/workflow/builtins.d.ts +32 -0
  113. package/dist/src/internal/workflow/builtins.js +1 -1
  114. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.d.ts +1 -1
  115. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +1 -1
  116. package/dist/src/internal/workflow-bundle/workflow-core-shim.d.ts +34 -0
  117. package/dist/src/internal/workflow-bundle/workflow-core-shim.js +1 -1
  118. package/dist/src/internal/workflow-bundle/workflow-transformer.js +1 -1
  119. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  120. package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +2 -2
  121. package/dist/src/packages/ash-scaffold/src/web-template.js +1 -0
  122. package/dist/src/public/channels/discord/discordChannel.d.ts +5 -2
  123. package/dist/src/public/channels/index.d.ts +1 -1
  124. package/dist/src/public/channels/slack/attachments.js +1 -1
  125. package/dist/src/public/channels/slack/index.d.ts +1 -1
  126. package/dist/src/public/channels/slack/slackChannel.d.ts +12 -8
  127. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  128. package/dist/src/public/channels/teams/teamsChannel.d.ts +5 -2
  129. package/dist/src/public/channels/telegram/telegramChannel.d.ts +5 -2
  130. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  131. package/dist/src/public/channels/twilio/index.d.ts +1 -1
  132. package/dist/src/public/channels/twilio/twilioChannel.d.ts +12 -3
  133. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  134. package/dist/src/public/definitions/defineChannel.d.ts +17 -4
  135. package/dist/src/public/definitions/defineChannel.js +1 -1
  136. package/dist/src/public/definitions/hook.d.ts +3 -11
  137. package/dist/src/public/definitions/instrumentation.d.ts +1 -66
  138. package/dist/src/public/definitions/instrumentation.js +1 -1
  139. package/dist/src/public/definitions/skill.d.ts +5 -0
  140. package/dist/src/public/definitions/tool.d.ts +25 -66
  141. package/dist/src/public/definitions/tool.js +1 -1
  142. package/dist/src/public/instrumentation/index.d.ts +175 -1
  143. package/dist/src/public/instrumentation/index.js +1 -1
  144. package/dist/src/public/sandbox/index.d.ts +1 -0
  145. package/dist/src/public/skills/index.d.ts +2 -0
  146. package/dist/src/public/skills/index.js +1 -1
  147. package/dist/src/public/tools/index.d.ts +2 -2
  148. package/dist/src/public/tools/index.js +1 -1
  149. package/dist/src/runtime/agent/mock-model-adapter.js +4 -7
  150. package/dist/src/runtime/agent/mock-model-skill-selection.d.ts +9 -0
  151. package/dist/src/runtime/agent/mock-model-skill-selection.js +4 -0
  152. package/dist/src/runtime/attributes/emit.d.ts +73 -0
  153. package/dist/src/runtime/attributes/emit.js +1 -0
  154. package/dist/src/runtime/channels/registry.js +1 -1
  155. package/dist/src/runtime/connections/mcp-client.js +1 -1
  156. package/dist/src/runtime/framework-tools/code-mode-connection-auth.d.ts +2 -0
  157. package/dist/src/runtime/framework-tools/connection-search-dynamic.d.ts +34 -0
  158. package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -0
  159. package/dist/src/runtime/framework-tools/index.d.ts +7 -5
  160. package/dist/src/runtime/framework-tools/index.js +1 -1
  161. package/dist/src/runtime/prompt/connections.js +1 -1
  162. package/dist/src/runtime/resolve-agent-graph.js +1 -1
  163. package/dist/src/runtime/resolve-agent.js +1 -1
  164. package/dist/src/runtime/resolve-channel.js +1 -1
  165. package/dist/src/runtime/resolve-dynamic-skill.d.ts +8 -0
  166. package/dist/src/runtime/resolve-dynamic-skill.js +1 -0
  167. package/dist/src/runtime/resolve-dynamic-tool.js +1 -1
  168. package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
  169. package/dist/src/runtime/sessions/runtime-context-keys.js +1 -1
  170. package/dist/src/runtime/types.d.ts +13 -4
  171. package/dist/src/shared/dynamic-tool-definition.d.ts +51 -76
  172. package/dist/src/shared/dynamic-tool-definition.js +1 -1
  173. package/dist/src/shared/guards.d.ts +14 -0
  174. package/dist/src/shared/guards.js +1 -1
  175. package/dist/src/shared/sandbox-network-policy.d.ts +23 -0
  176. package/dist/src/shared/sandbox-network-policy.js +1 -0
  177. package/dist/src/shared/sandbox-session.d.ts +15 -0
  178. package/dist/src/shared/skill-definition.d.ts +5 -4
  179. package/dist/src/shared/tool-definition.d.ts +12 -0
  180. package/package.json +2 -1
  181. package/dist/src/runtime/framework-tools/connection-search.d.ts +0 -57
  182. package/dist/src/runtime/framework-tools/connection-search.js +0 -1
  183. package/dist/src/runtime/framework-tools/connection-tools.d.ts +0 -55
  184. package/dist/src/runtime/framework-tools/connection-tools.js +0 -1
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{parseJsonObject}from"#shared/json.js";import{defaultDeliverResult}from"#channel/adapter.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,i={...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(i,{send:a,waitUntil:o})=>{let s=await verifyInbound(i,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:i});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,modelContext:r.modelContext},{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,modelContext:e.modelContext}:e.message===void 0?void 0:{message:e.message,modelContext:e.modelContext}}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{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,modelContext:r.modelContext},{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,modelContext:e.modelContext}:e.message===void 0?void 0:{message:e.message,modelContext:e.modelContext}}return defaultDeliverResult(e)}}function stateFromMessage(e,t){let n=e.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:e.chat.id,chatType:e.chat.type,conversationId:n?null:conversationIdForMessage(e),messageThreadId:e.messageThreadId??null,triggeringUserId:e.from?.id??null}}function stateFromCallbackQuery(e,t){let n=e.message;if(!n)return{...initialTelegramState(t.botUsername),triggeringUserId:e.from.id};let r=n.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:n.chat.id,chatType:n.chat.type,conversationId:r?null:n.messageId,messageThreadId:n.messageThreadId??null,triggeringUserId:e.from.id}}function conversationIdForMessage(e){return e.replyToMessage?.from?.isBot===!0?e.replyToMessage.messageId:e.messageId}function continuationTokenFromState(e){return telegramContinuationToken({chatId:e.chatId??``,conversationId:e.chatType===`private`?void 0:e.conversationId??void 0,messageThreadId:e.messageThreadId??void 0})}function initialTelegramState(e){return{botUsername:e??null,chatId:null,chatType:null,conversationId:null,hitlCallbacks:{},messageThreadId:null,nextHitlCallbackId:0,pendingFreeformReplies:{},triggeringUserId:null}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}function readOptionalString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
@@ -1,4 +1,4 @@
1
- export { twilioChannel, type TwilioAllowFrom, type TwilioChannel, type TwilioChannelConfig, type TwilioChannelCredentials, type TwilioChannelEvents, type TwilioChannelState, type TwilioContext, type TwilioEventContext, type TwilioHandle, type TwilioInboundResult, type TwilioInboundResultOrPromise, type TwilioMessagingConfig, type TwilioReceiveArgs, type TwilioSendMessageOptions, type TwilioVoiceConfig, type TwilioVoiceResult, type TwilioVoiceResultOrPromise, } from "#public/channels/twilio/twilioChannel.js";
1
+ export { twilioChannel, type TwilioAllowFrom, type TwilioChannel, type TwilioChannelConfig, type TwilioChannelCredentials, type TwilioChannelEvents, type TwilioChannelState, type TwilioContext, type TwilioEventContext, type TwilioHandle, type TwilioInboundResult, type TwilioInboundResultOrPromise, type TwilioInstrumentationMetadata, type TwilioMessagingConfig, type TwilioReceiveArgs, type TwilioSendMessageOptions, type TwilioVoiceConfig, type TwilioVoiceResult, type TwilioVoiceResultOrPromise, } from "#public/channels/twilio/twilioChannel.js";
2
2
  export { callTwilioApi, resolveTwilioAccountSid, sendTwilioMessage, twilioContinuationToken, updateTwilioCall, type TwilioAccountSid, type TwilioApiOptions, type TwilioApiResponse, type TwilioCredentials, type TwilioFetch, type TwilioSendMessageInput, type TwilioUpdateCallInput, } from "#public/channels/twilio/api.js";
3
3
  export type { TwilioInboundContext, TwilioTextMessage, TwilioVoiceCall, TwilioVoiceTranscription, } from "#public/channels/twilio/inbound.js";
4
4
  export { emptyTwilioResponse, escapeXml, gatherSpeechTwilioResponse, sayTwilioResponse, twimlResponse, type TwilioGatherTwimlOptions, } from "#public/channels/twilio/twiml.js";
@@ -15,10 +15,13 @@ type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessa
15
15
  export interface TwilioContext {
16
16
  readonly twilio: TwilioHandle;
17
17
  }
18
- /** Event-handler Twilio context, including mutable per-phone channel state. */
19
- export interface TwilioEventContext extends TwilioContext, ChannelSessionOps {
18
+ /** Channel-owned Twilio context returned by `context()`. */
19
+ export interface TwilioChannelContext extends TwilioContext {
20
20
  state: TwilioChannelState;
21
21
  }
22
+ /** Event-handler Twilio context, including session operations. */
23
+ export interface TwilioEventContext extends TwilioChannelContext, ChannelSessionOps {
24
+ }
22
25
  /** JSON-serializable state for the phone-number conversation. */
23
26
  export interface TwilioChannelState {
24
27
  /** Caller / sender phone number. */
@@ -30,6 +33,12 @@ export interface TwilioChannelState {
30
33
  /** Most recent inbound Call SID when this session was started by voice. */
31
34
  lastCallSid?: string | null;
32
35
  }
36
+ export interface TwilioInstrumentationMetadata extends Record<string, unknown> {
37
+ readonly from: string | null;
38
+ readonly lastCallSid: string | null;
39
+ readonly lastMessageSid: string | null;
40
+ readonly to: string | null;
41
+ }
33
42
  /** Twilio channel credentials. `authToken` also verifies inbound webhook signatures. */
34
43
  export interface TwilioChannelCredentials extends TwilioCredentials {
35
44
  readonly authToken?: TwilioAuthToken;
@@ -174,7 +183,7 @@ export interface TwilioSendMessageOptions {
174
183
  readonly statusCallbackUrl?: string;
175
184
  }
176
185
  /** Concrete return type of {@link twilioChannel}. */
177
- export interface TwilioChannel extends Channel<TwilioChannelState, TwilioReceiveArgs> {
186
+ export interface TwilioChannel extends Channel<TwilioChannelState, TwilioReceiveArgs, TwilioInstrumentationMetadata> {
178
187
  }
179
188
  /** Twilio channel factory for SMS and speech-transcribed inbound calls. */
180
189
  export declare function twilioChannel(config: TwilioChannelConfig): TwilioChannel;
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{verifyTwilioRequest}from"#public/channels/twilio/verify.js";import{callTwilioApi,sendTwilioMessage,twilioContinuationToken,updateTwilioCall}from"#public/channels/twilio/api.js";import{emptyTwilioResponse,gatherSpeechTwilioResponse,sayTwilioResponse}from"#public/channels/twilio/twiml.js";import{defaultEvents,defaultOnText,defaultOnVoice,defaultOnVoiceTranscription}from"#public/channels/twilio/defaults.js";import{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},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{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};
@@ -6,6 +6,7 @@ import type { HandleMessageStreamEvent } from "#protocol/message.js";
6
6
  import type { SessionContext } from "#public/definitions/callback-context.js";
7
7
  import type { RouteDefinition, SendFn } from "#channel/routes.js";
8
8
  import type { Session, SessionHandle } from "#channel/session.js";
9
+ declare const CHANNEL_METADATA_TYPE: unique symbol;
9
10
  export type { Session, SessionHandle } from "#channel/session.js";
10
11
  export { GET, POST, PUT, PATCH, DELETE } from "#channel/routes.js";
11
12
  export type { RouteDefinition, RouteHandlerArgs, SendFn, SendOptions, SendPayload, GetSessionFn, } from "#channel/routes.js";
@@ -53,7 +54,7 @@ export interface ReceiveInput<TReceiveArgs = Record<string, unknown>> {
53
54
  readonly args: Readonly<TReceiveArgs>;
54
55
  readonly auth: SessionAuthContext | null;
55
56
  }
56
- export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>> {
57
+ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> {
57
58
  readonly state?: TState;
58
59
  /**
59
60
  * Builds the per-step channel context handed to `events` and
@@ -68,7 +69,7 @@ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs
68
69
  * (with {@link ChannelSessionOps} injected) and passes
69
70
  * {@link SessionContext} as a separate `ctx` argument.
70
71
  */
71
- context?(state: NonNullable<TState>, session: SessionHandle): Omit<TCtx, keyof ChannelSessionOps>;
72
+ context?(state: NonNullable<TState>, session: SessionHandle): TCtx;
72
73
  readonly routes: readonly RouteDefinition<TState>[];
73
74
  receive?(input: ReceiveInput<TReceiveArgs>, args: {
74
75
  send: SendFn<TState>;
@@ -84,6 +85,16 @@ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs
84
85
  * stage the file to the sandbox.
85
86
  */
86
87
  readonly fetchFile?: (url: string) => Promise<Buffer | FetchFileResult | null>;
88
+ /**
89
+ * Channel-owned metadata exposed to instrumentation callbacks.
90
+ *
91
+ * Keep this projection intentionally small. It is the channel's public
92
+ * observability surface, not a dump of durable adapter state. Return an
93
+ * object composed of JSON primitives, arrays, and plain objects. Ash omits
94
+ * `undefined` object properties and drops projections containing values such
95
+ * as `Date` or `Map`.
96
+ */
97
+ readonly metadata?: (state: NonNullable<TState>) => TMetadata;
87
98
  /**
88
99
  * Identifier of the adapter family this channel belongs to. Set by
89
100
  * higher-level wrappers (e.g. `slackChannel` passes `"slack"`) so
@@ -97,8 +108,9 @@ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs
97
108
  */
98
109
  readonly kindHint?: string;
99
110
  }
100
- export interface Channel<TState = undefined, TReceiveArgs = Record<string, unknown>> extends TypedReceiveRoute<TReceiveArgs> {
111
+ export interface Channel<TState = undefined, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> extends TypedReceiveRoute<TReceiveArgs> {
101
112
  readonly __kind: typeof CHANNEL_SENTINEL;
113
+ readonly [CHANNEL_METADATA_TYPE]?: TMetadata;
102
114
  readonly routes: readonly {
103
115
  method: string;
104
116
  path: string;
@@ -107,4 +119,5 @@ export interface Channel<TState = undefined, TReceiveArgs = Record<string, unkno
107
119
  send: SendFn<TState>;
108
120
  }) => Promise<Session>;
109
121
  }
110
- export declare function defineChannel<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>>(definition: ChannelDefinition<TState, TCtx, TReceiveArgs>): Channel<TState, TReceiveArgs>;
122
+ export type InferChannelMetadata<TChannel> = TChannel extends Channel<any, any, infer TMetadata> ? TMetadata : Record<string, unknown>;
123
+ export declare function defineChannel<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>>(definition: ChannelDefinition<TState, TCtx, TReceiveArgs, TMetadata>): Channel<TState, TReceiveArgs, TMetadata>;
@@ -1 +1 @@
1
- import{CHANNEL_SENTINEL}from"#channel/compiled-channel.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{defaultDeliverResult}from"#channel/adapter.js";import{HTTP_ADAPTER_KIND}from"#channel/http.js";import{DELETE,GET,PATCH,POST,PUT}from"#channel/routes.js";function defineChannel(t){let n=buildAdapter(t);return{__kind:CHANNEL_SENTINEL,routes:t.routes,adapter:n,receive:t.receive}}function buildAdapter(e){let i=e.state!=null,a=e.context!=null,o=e.fetchFile!==void 0,s=i||a,c={},l=!1,u=[`turn.started`,`actions.requested`,`action.result`,`message.completed`,`message.appended`,`input.requested`,`turn.failed`,`turn.completed`,`session.failed`,`session.completed`,`session.waiting`,`authorization.required`,`authorization.completed`],d=e.events;for(let e of u){let n=d?.[e];n&&(l=!0,c[e]=(r,i)=>{let a={...i,continuationToken:i.session?.continuationToken??``,setContinuationToken:e=>i.session?.setContinuationToken(e)};return e===`session.failed`?n(r,a):n(r,a,buildCallbackContext())})}return!s&&!l&&!o?{kind:e.kindHint??HTTP_ADAPTER_KIND}:{kind:e.kindHint??`defineChannel`,state:i?{...e.state}:{},fetchFile:e.fetchFile,createAdapterContext(t){let n=t.state,r=t.session;return{...a?e.context(n,r):{},state:n,ctx:t.ctx,session:r}},deliver(e){return defaultDeliverResult(e)},...c}}export{DELETE,GET,PATCH,POST,PUT,defineChannel};
1
+ import{CHANNEL_SENTINEL}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{HTTP_ADAPTER_KIND}from"#channel/http.js";import{DELETE,GET,PATCH,POST,PUT}from"#channel/routes.js";function defineChannel(t){let n=buildAdapter(t);return{__kind:CHANNEL_SENTINEL,routes:t.routes,adapter:n,receive:t.receive}}function buildAdapter(e){let i=e.state!=null,a=e.context!=null,o=e.fetchFile!==void 0,s=e.metadata,c=i||a||s!==void 0,l={},u=!1,d=[`turn.started`,`actions.requested`,`action.result`,`message.completed`,`message.appended`,`input.requested`,`turn.failed`,`turn.completed`,`session.failed`,`session.completed`,`session.waiting`,`authorization.required`,`authorization.completed`],f=e.events;for(let e of d){let t=f?.[e];t&&(u=!0,l[e]=(r,i)=>{let a={...i,continuationToken:i.session?.continuationToken??``,setContinuationToken:e=>i.session?.setContinuationToken(e)};return e===`session.failed`?t(r,a):t(r,a,buildCallbackContext())})}return!c&&!u&&!o?{kind:e.kindHint??HTTP_ADAPTER_KIND}:{kind:e.kindHint??`defineChannel`,state:i?{...e.state}:{},fetchFile:e.fetchFile,instrumentation:s===void 0?void 0:{metadata(e){return s(e)}},createAdapterContext(t){let n=t.state,r=t.session;return{...a?e.context(n,r):{},state:n,ctx:t.ctx,session:r}},deliver(e){return defaultDeliverResult(e)},...l}}export{DELETE,GET,PATCH,POST,PUT,defineChannel};
@@ -2,7 +2,6 @@ import type { ModelMessage } from "ai";
2
2
  import type { HandleMessageStreamEvent } from "../../protocol/message.js";
3
3
  import type { SessionContext } from "./callback-context.js";
4
4
  import type { ExactDefinition } from "./exact.js";
5
- import type { NamedSkillDefinition } from "./skill.js";
6
5
  /**
7
6
  * Context passed to every hook handler.
8
7
  *
@@ -24,22 +23,15 @@ export interface HookContext extends SessionContext {
24
23
  * `lifecycle.turn`).
25
24
  *
26
25
  * `modelContext` messages are appended to the next model call's message
27
- * list and never written to durable session history. `skills` are
28
- * materialized into the live sandbox and announced through durable
29
- * system history. Each lifecycle key runs in registry order;
30
- * contributions are concatenated session-then-turn before the harness's
31
- * first model call.
26
+ * list and never written to durable session history. Each lifecycle key
27
+ * runs in registry order; contributions are concatenated session-then-turn
28
+ * before the harness's first model call.
32
29
  *
33
30
  * Returning `void` (or `undefined`) means "no opinion" — the turn
34
31
  * proceeds with no additional model context from this hook.
35
32
  */
36
33
  export interface LifecycleHookResult {
37
34
  readonly modelContext?: readonly ModelMessage[];
38
- /**
39
- * Skill packages to materialize into the live durable sandbox before the
40
- * next model call. Authored skill names cannot be overwritten.
41
- */
42
- readonly skills?: readonly NamedSkillDefinition[];
43
35
  }
44
36
  /**
45
37
  * Lifecycle hook signature shared by `lifecycle.session` and
@@ -1,66 +1 @@
1
- import type { ExactDefinition } from "#public/definitions/exact.js";
2
- /**
3
- * Context passed to the {@link InstrumentationDefinition.setup} callback.
4
- */
5
- export interface InstrumentationSetupContext {
6
- /**
7
- * The agent name declared by `defineAgent`.
8
- *
9
- * Use this as the `serviceName` for `registerOTel` instead of
10
- * hard-coding a string — it prevents copy-paste drift across agents.
11
- */
12
- readonly agentName: string;
13
- }
14
- /**
15
- * Authored instrumentation settings accepted by `defineInstrumentation`.
16
- *
17
- * The presence of a `defineInstrumentation` export implicitly enables
18
- * telemetry — there is no separate `isEnabled` toggle.
19
- */
20
- export interface InstrumentationDefinition {
21
- /**
22
- * Override the default function identifier attached to telemetry spans.
23
- *
24
- * When omitted, Ash derives the identifier from the agent name.
25
- */
26
- readonly functionId?: string;
27
- /**
28
- * Additional metadata merged into every AI SDK telemetry span.
29
- */
30
- readonly metadata?: Record<string, string>;
31
- /**
32
- * Whether to record full model inputs in telemetry spans.
33
- *
34
- * Defaults to `true` when `instrumentation.ts` is present. Set to `false`
35
- * to disable recording inputs, which may be useful if inputs contain
36
- * sensitive content or you want to reduce span payload size.
37
- */
38
- readonly recordInputs?: boolean;
39
- /**
40
- * Whether to record full model outputs in telemetry spans.
41
- *
42
- * Defaults to `true` when `instrumentation.ts` is present. Set to `false`
43
- * to disable recording outputs.
44
- */
45
- readonly recordOutputs?: boolean;
46
- /**
47
- * Optional setup callback invoked at server startup with the resolved
48
- * agent name.
49
- *
50
- * Use this to call `registerOTel` or any other OTel provider setup.
51
- * The `context.agentName` value comes from `defineAgent`, so you never
52
- * need to hard-code a service name.
53
- */
54
- readonly setup?: (context: InstrumentationSetupContext) => void;
55
- }
56
- /**
57
- * Defines instrumentation settings for the agent application.
58
- *
59
- * Export the result as the default export of `agent/instrumentation.ts`.
60
- * Ash picks up these settings at server startup and applies them to every
61
- * AI SDK model call.
62
- *
63
- * The `setup` callback is invoked later by the framework with the resolved
64
- * agent name — it is not called during `defineInstrumentation` itself.
65
- */
66
- export declare function defineInstrumentation<T extends InstrumentationDefinition>(definition: ExactDefinition<T, InstrumentationDefinition>): T;
1
+ export * from "#public/instrumentation/index.js";
@@ -1 +1 @@
1
- function defineInstrumentation(e){return e}export{defineInstrumentation};
1
+ export*from"#public/instrumentation/index.js";export{};
@@ -12,5 +12,10 @@ export type SkillDefinition = SkillPackageDefinition;
12
12
  /**
13
13
  * Defines a skill in TypeScript using the same shape discovery produces from
14
14
  * markdown, with optional package-relative sibling files.
15
+ *
16
+ * When used as the default export of a file in `agent/skills/`, this
17
+ * produces a static skill. When used inside a `defineDynamic` handler,
18
+ * the brand stamp lets the lifecycle code detect single-entry vs
19
+ * map-of-entries return shapes.
15
20
  */
16
21
  export declare function defineSkill<TSkill extends SkillDefinition>(definition: ExactDefinition<TSkill, SkillDefinition>): TSkill;
@@ -1,9 +1,9 @@
1
1
  import type { StandardJSONSchemaV1 } from "#compiled/@standard-schema/spec/index.js";
2
2
  import type { ModelMessage } from "ai";
3
- import type { PublicToolDefinition } from "#shared/tool-definition.js";
3
+ import type { PublicToolDefinition, ToolModelOutput } from "#shared/tool-definition.js";
4
4
  import type { SessionContext } from "#public/definitions/callback-context.js";
5
5
  import type { JsonObject } from "#shared/json.js";
6
- import { type DynamicSingleToolEvents, type DynamicToolEntry, type DynamicToolEvents, type DynamicToolSentinel, type DynamicToolsSentinel } from "#shared/dynamic-tool-definition.js";
6
+ import { type DynamicEvents, type DynamicSentinel } from "#shared/dynamic-tool-definition.js";
7
7
  /**
8
8
  * Domain input passed to a tool's {@link ToolDefinition.onCompact} hook.
9
9
  */
@@ -39,19 +39,7 @@ export interface NeedsApprovalContext<TInput = Record<string, unknown>> {
39
39
  readonly toolInput?: ApprovalToolInput<TInput>;
40
40
  readonly toolName: string;
41
41
  }
42
- /**
43
- * Ash-owned shape for the model-facing tool result produced by
44
- * {@link ToolDefinition.toModelOutput}. Structurally compatible with the
45
- * AI SDK's `ToolResultOutput` so the harness can forward it without
46
- * conversion.
47
- */
48
- export type ToolModelOutput = {
49
- readonly type: "text";
50
- readonly value: string;
51
- } | {
52
- readonly type: "json";
53
- readonly value: unknown;
54
- };
42
+ export type { ToolModelOutput } from "#shared/tool-definition.js";
55
43
  /**
56
44
  * Result returned from a {@link ToolRetentionPolicy} function.
57
45
  *
@@ -140,6 +128,13 @@ export type ToolDefinition<TInput = unknown, TOutput = unknown> = PublicToolDefi
140
128
  };
141
129
  /**
142
130
  * Defines a tool configuration.
131
+ *
132
+ * Used for both static tools (default export from `agent/tools/*.ts`)
133
+ * and as the entry wrapper inside `defineDynamic` resolvers.
134
+ *
135
+ * For static tools, the runtime tool name is the filename slug.
136
+ * For dynamic entries, `defineTool` stamps a brand that the
137
+ * lifecycle code validates — raw object literals are rejected.
143
138
  */
144
139
  export declare function defineTool<TSchema extends StandardJSONSchemaV1<unknown, unknown>, TOutput>(definition: {
145
140
  description: ToolDefinition<unknown, unknown>["description"];
@@ -160,72 +155,37 @@ export declare function defineTool<TOutput>(definition: {
160
155
  onCompact?: ToolDefinition<unknown, unknown>["onCompact"];
161
156
  }): ToolDefinition<Record<string, unknown>, TOutput>;
162
157
  export declare function defineTool<TInput = unknown, TOutput = unknown>(definition: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
163
- export declare function defineTool<TInput = unknown, TOutput = unknown>(definition: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
164
- export declare function defineTool(definition: {
165
- readonly events: DynamicSingleToolEvents;
166
- }): DynamicToolSentinel;
167
158
  /**
168
159
  * Defines a dynamic tool set resolved at runtime. The file's
169
- * path-derived slug becomes the tool name prefix; entry keys become
170
- * suffixes joined by `__`. When the resolver returns exactly one
171
- * entry, the tool takes the file's name directly.
172
- *
173
- * Uses the same `events` object shape as {@link defineHook}:
160
+ * path-derived slug becomes the tool name; the handler's return
161
+ * shape determines naming:
174
162
  *
175
- * ```ts
176
- * export default defineTools({
177
- * events: {
178
- * "session.started": async (event, ctx) => ({
179
- * export: { description: "...", inputSchema: {...}, execute: ... },
180
- * query: { description: "...", inputSchema: {...}, execute: ... },
181
- * }),
182
- * },
183
- * });
184
- * ```
185
- */
186
- export declare function defineTools(definition: {
187
- readonly events: DynamicToolEvents;
188
- }): DynamicToolsSentinel;
189
- /**
190
- * Typed wrapper for a single entry inside a `defineTools` resolver.
191
- * Mirrors the AI SDK's `tool()` pattern — an identity function at
192
- * runtime that captures `TSchema` at the type level so `execute(input)`
193
- * is inferred from the schema.
163
+ * - Return a single `defineTool(...)` → tool named after the file slug.
164
+ * - Return a `Record<string, defineTool(...)>` → tools named `slug__key`.
165
+ * - Return `null` → no tools for this event.
194
166
  *
195
167
  * ```ts
196
- * import { defineTools, tool } from "experimental-ash/tools";
168
+ * import { defineDynamic, defineTool } from "experimental-ash/tools";
197
169
  * import { z } from "zod";
198
170
  *
199
- * export default defineTools({
171
+ * export default defineDynamic({
200
172
  * events: {
201
- * "session.started": async () => ({
202
- * weather: tool({
203
- * description: "Get weather",
204
- * inputSchema: z.object({ city: z.string() }),
173
+ * "session.started": async (event, ctx) => ({
174
+ * export: defineTool({
175
+ * description: "Export data",
176
+ * inputSchema: z.object({ format: z.string() }),
205
177
  * async execute(input) {
206
- * // input.city is typed as string
207
- * return fetchWeather(input.city);
178
+ * return doExport(input.format);
208
179
  * },
209
180
  * }),
210
181
  * }),
211
182
  * },
212
183
  * });
213
184
  * ```
214
- *
215
- * Without `tool()`, `execute(input)` defaults to
216
- * `Record<string, unknown>`. Use `tool()` when you want schema-
217
- * inferred types.
218
185
  */
219
- export declare function tool<TSchema extends StandardJSONSchemaV1<unknown, unknown>, TOutput>(entry: {
220
- description: string;
221
- inputSchema: TSchema;
222
- execute(input: StandardJSONSchemaV1.InferOutput<TSchema>, ctx: ToolContext): TOutput | Promise<TOutput>;
223
- }): DynamicToolEntry<StandardJSONSchemaV1.InferOutput<TSchema>, TOutput>;
224
- export declare function tool<TOutput>(entry: {
225
- description: string;
226
- inputSchema: JsonObject;
227
- execute(input: Record<string, unknown>, ctx: ToolContext): TOutput | Promise<TOutput>;
228
- }): DynamicToolEntry<Record<string, unknown>, TOutput>;
186
+ export declare function defineDynamic(definition: {
187
+ readonly events: DynamicEvents;
188
+ }): DynamicSentinel;
229
189
  /**
230
190
  * Marker discriminator written into every {@link DisabledToolSentinel}.
231
191
  */
@@ -248,4 +208,3 @@ export declare function disableTool(): DisabledToolSentinel;
248
208
  * produced by {@link disableTool}.
249
209
  */
250
210
  export declare function isDisabledToolSentinel(value: unknown): value is DisabledToolSentinel;
251
- export {};
@@ -1 +1 @@
1
- import{DYNAMIC_TOOLS_SENTINEL_KIND,DYNAMIC_TOOL_SENTINEL_KIND}from"#shared/dynamic-tool-definition.js";import{stampDefinitionKey}from"#public/tool-result-narrowing.js";function defineTool(e){if(`events`in e){let n={kind:DYNAMIC_TOOL_SENTINEL_KIND,events:e.events};return stampDefinitionKey(n,`dynamic-tool:${Object.keys(e.events).join(`,`)}`),n}return stampDefinitionKey(e,`tool:${e.description}`),e}function defineTools(t){let n={kind:DYNAMIC_TOOLS_SENTINEL_KIND,events:t.events};return stampDefinitionKey(n,`dynamic-tools:${Object.keys(t.events).join(`,`)}`),n}function tool(e){return Object.assign(e,{__brand:`ash:tool`})}const DISABLED_TOOL_SENTINEL_KIND=`ash:disabled-tool`;function disableTool(){return{kind:DISABLED_TOOL_SENTINEL_KIND}}function isDisabledToolSentinel(e){return typeof e==`object`&&!!e&&e.kind===DISABLED_TOOL_SENTINEL_KIND}export{defineTool,defineTools,disableTool,isDisabledToolSentinel,tool};
1
+ import{DYNAMIC_SENTINEL_KIND,TOOL_BRAND}from"#shared/dynamic-tool-definition.js";import{stampDefinitionKey}from"#public/tool-result-narrowing.js";function defineTool(e){return Object.assign(e,{[TOOL_BRAND]:!0}),stampDefinitionKey(e,`tool:${e.description}`),e}function defineDynamic(t){let n={kind:DYNAMIC_SENTINEL_KIND,events:t.events};return stampDefinitionKey(n,`dynamic:${Object.keys(t.events).join(`,`)}`),n}const DISABLED_TOOL_SENTINEL_KIND=`ash:disabled-tool`;function disableTool(){return{kind:DISABLED_TOOL_SENTINEL_KIND}}function isDisabledToolSentinel(e){return typeof e==`object`&&!!e&&e.kind===DISABLED_TOOL_SENTINEL_KIND}export{defineDynamic,defineTool,disableTool,isDisabledToolSentinel};
@@ -1,4 +1,178 @@
1
+ import type { ExactDefinition } from "#public/definitions/exact.js";
1
2
  /**
2
3
  * Instrumentation authoring helpers for `agent/instrumentation.ts`.
3
4
  */
4
- export { defineInstrumentation, type InstrumentationDefinition, type InstrumentationSetupContext, } from "#public/definitions/instrumentation.js";
5
+ import type { ModelMessage, SystemModelMessage } from "ai";
6
+ import type { SessionAuthContext, SessionParent } from "#channel/types.js";
7
+ import type { Channel } from "#public/definitions/defineChannel.js";
8
+ /**
9
+ * Context passed to the {@link InstrumentationDefinition.setup} callback.
10
+ */
11
+ export interface InstrumentationSetupContext {
12
+ /**
13
+ * The agent name declared by `defineAgent`.
14
+ *
15
+ * Use this as the `serviceName` for `registerOTel` instead of
16
+ * hard-coding a string — it prevents copy-paste drift across agents.
17
+ */
18
+ readonly agentName: string;
19
+ }
20
+ /**
21
+ * User-authored metadata attached to AI SDK telemetry spans.
22
+ *
23
+ * Keys beginning with `ash.` are reserved for framework-owned metadata
24
+ * and are ignored when returned from authored instrumentation.
25
+ */
26
+ export type InstrumentationMetadata = Readonly<Record<string, string>>;
27
+ /**
28
+ * Base channel metadata shape used by framework channel kinds.
29
+ */
30
+ export type InstrumentationChannelMetadata = Readonly<Record<string, unknown>>;
31
+ /**
32
+ * Channel metadata projections keyed by instrumentation channel kind.
33
+ *
34
+ * Channel packages and user-authored channels declaration-merge additional
35
+ * `channel:<registered-name>` entries into this map so `input.channel.metadata`
36
+ * narrows after checking `input.channel.kind`.
37
+ */
38
+ export interface ChannelMetadataMap {
39
+ readonly http: InstrumentationChannelMetadata;
40
+ readonly schedule: InstrumentationChannelMetadata;
41
+ readonly subagent: InstrumentationChannelMetadata;
42
+ readonly unknown: InstrumentationChannelMetadata;
43
+ }
44
+ export type InstrumentationChannelKind = keyof ChannelMetadataMap;
45
+ /**
46
+ * Authored channel values keyed by path-derived instrumentation channel kind.
47
+ *
48
+ * Ash generates declaration-merged entries for authored channel files. Use
49
+ * this map through {@link isChannel}; authored code should not merge it by
50
+ * hand except for unusual setups outside the compiler-owned path.
51
+ */
52
+ export interface ChannelReferenceMap {
53
+ }
54
+ /**
55
+ * Channel projection exposed to instrumentation callbacks.
56
+ *
57
+ * `kind` is the channel identity Ash has for the current turn (`"http"`,
58
+ * `"schedule"`, `"subagent"`, or `channel:<name>` for authored channels,
59
+ * where `<name>` is the channel's filename under `agent/channels/`).
60
+ */
61
+ export interface InstrumentationChannelForKind<K extends InstrumentationChannelKind> {
62
+ readonly kind: K;
63
+ readonly metadata: ChannelMetadataMap[K];
64
+ }
65
+ export type InstrumentationChannel = {
66
+ readonly [K in InstrumentationChannelKind]: InstrumentationChannelForKind<K>;
67
+ }[InstrumentationChannelKind];
68
+ type ChannelReferenceKind<TChannel> = {
69
+ readonly [K in keyof ChannelReferenceMap]: [TChannel] extends [ChannelReferenceMap[K]] ? [ChannelReferenceMap[K]] extends [TChannel] ? K : never : never;
70
+ }[keyof ChannelReferenceMap];
71
+ export type InstrumentationChannelForChannel<TChannel> = Extract<InstrumentationChannel, {
72
+ readonly kind: Extract<ChannelReferenceKind<TChannel>, InstrumentationChannelKind>;
73
+ }>;
74
+ export interface InstrumentationSession {
75
+ readonly auth: {
76
+ readonly current: SessionAuthContext | null;
77
+ readonly initiator: SessionAuthContext | null;
78
+ };
79
+ readonly id: string;
80
+ readonly parent?: SessionParent;
81
+ }
82
+ export interface InstrumentationTurn {
83
+ readonly id: string;
84
+ readonly sequence: number;
85
+ }
86
+ export interface InstrumentationStep {
87
+ readonly index: number;
88
+ }
89
+ export interface InstrumentationModelInput {
90
+ readonly instructions: string | readonly SystemModelMessage[] | undefined;
91
+ readonly messages: readonly ModelMessage[];
92
+ }
93
+ /**
94
+ * Input passed to `metadata["step.started"]`.
95
+ *
96
+ * The callback runs after Ash has built the final model input for this
97
+ * model-call attempt and before the AI SDK model call is constructed.
98
+ */
99
+ export interface InstrumentationStepStartedMetadataInput {
100
+ readonly channel: InstrumentationChannel;
101
+ readonly modelInput: InstrumentationModelInput;
102
+ readonly session: InstrumentationSession;
103
+ readonly step: InstrumentationStep;
104
+ readonly turn: InstrumentationTurn;
105
+ }
106
+ /**
107
+ * Metadata hooks accepted by {@link defineInstrumentation}.
108
+ */
109
+ export interface InstrumentationMetadataConfig {
110
+ /**
111
+ * Per-attempt metadata resolved before the model call so child spans
112
+ * created by the AI SDK inherit the returned values.
113
+ */
114
+ readonly "step.started"?: (input: InstrumentationStepStartedMetadataInput) => InstrumentationMetadata;
115
+ }
116
+ /**
117
+ * Authored instrumentation settings accepted by `defineInstrumentation`.
118
+ *
119
+ * The presence of a `defineInstrumentation` export implicitly enables
120
+ * telemetry — there is no separate `isEnabled` toggle.
121
+ */
122
+ export interface InstrumentationDefinition {
123
+ /**
124
+ * Override the default function identifier attached to telemetry spans.
125
+ *
126
+ * When omitted, Ash derives the identifier from the agent name.
127
+ */
128
+ readonly functionId?: string;
129
+ /**
130
+ * Additional metadata merged into AI SDK telemetry spans.
131
+ */
132
+ readonly metadata?: InstrumentationMetadataConfig;
133
+ /**
134
+ * Whether to record full model inputs in telemetry spans.
135
+ *
136
+ * Defaults to `true` when `instrumentation.ts` is present. Set to `false`
137
+ * to disable recording inputs, which may be useful if inputs contain
138
+ * sensitive content or you want to reduce span payload size.
139
+ */
140
+ readonly recordInputs?: boolean;
141
+ /**
142
+ * Whether to record full model outputs in telemetry spans.
143
+ *
144
+ * Defaults to `true` when `instrumentation.ts` is present. Set to `false`
145
+ * to disable recording outputs.
146
+ */
147
+ readonly recordOutputs?: boolean;
148
+ /**
149
+ * Optional setup callback invoked at server startup with the resolved
150
+ * agent name.
151
+ *
152
+ * Use this to call `registerOTel` or any other OTel provider setup.
153
+ * The `context.agentName` value comes from `defineAgent`, so you never
154
+ * need to hard-code a service name.
155
+ */
156
+ readonly setup?: (context: InstrumentationSetupContext) => void;
157
+ }
158
+ /**
159
+ * Defines instrumentation settings for the agent application.
160
+ *
161
+ * Export the result as the default export of `agent/instrumentation.ts`.
162
+ * Ash picks up these settings at server startup and applies them to every
163
+ * AI SDK model call.
164
+ *
165
+ * The `setup` callback is invoked later by the framework with the resolved
166
+ * agent name — it is not called during `defineInstrumentation` itself.
167
+ */
168
+ export declare function defineInstrumentation<T extends InstrumentationDefinition>(definition: ExactDefinition<T, InstrumentationDefinition>): T;
169
+ /**
170
+ * Narrows an instrumentation channel by comparing it to an app-owned channel
171
+ * value imported from `agent/channels/*`.
172
+ *
173
+ * The comparison uses the compiler's path-derived `channel:<slug>` identity.
174
+ * It does not read durable channel state; metadata remains the explicit
175
+ * projection returned by the channel's `metadata(state)` function.
176
+ */
177
+ export declare function isChannel<TChannel extends Channel<any, any, any>>(channel: InstrumentationChannel, target: TChannel): channel is InstrumentationChannelForChannel<TChannel>;
178
+ export {};