experimental-ash 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +7 -7
- package/dist/docs/public/README.md +10 -10
- package/dist/docs/public/{auth-and-route-protection.md → advanced/auth-and-route-protection.md} +1 -0
- package/dist/docs/public/{cli-build-and-debugging.md → advanced/cli-build-and-debugging.md} +1 -0
- package/dist/docs/public/{context-control.md → advanced/context-control.md} +1 -0
- package/dist/docs/public/{evals.md → advanced/evals.md} +1 -0
- package/dist/docs/public/{faqs.md → advanced/faqs.md} +1 -0
- package/dist/docs/public/{hooks.md → advanced/hooks.md} +1 -0
- package/dist/docs/public/{instrumentation.md → advanced/instrumentation.md} +1 -0
- package/dist/docs/public/advanced/meta.json +19 -0
- package/dist/docs/public/{project-layout.md → advanced/project-layout.md} +1 -0
- package/dist/docs/public/{runs-and-streaming.md → advanced/runs-and-streaming.md} +1 -0
- package/dist/docs/public/{session-context.md → advanced/session-context.md} +1 -0
- package/dist/docs/public/{typescript-api.md → advanced/typescript-api.md} +1 -0
- package/dist/docs/public/{vercel-deployment.md → advanced/vercel-deployment.md} +1 -0
- package/dist/docs/public/{workspace.md → advanced/workspace.md} +1 -0
- package/dist/docs/public/meta.json +1 -14
- package/dist/src/context/accessors.d.ts +0 -25
- package/dist/src/context/accessors.js +1 -1
- package/dist/src/context/build-callback-context.js +1 -1
- package/dist/src/context/keys.d.ts +2 -2
- package/dist/src/evals/define-eval-suite.d.ts +3 -2
- package/dist/src/evals/types.d.ts +38 -12
- package/dist/src/execution/skills/types.d.ts +1 -1
- package/dist/src/harness/step-hooks.d.ts +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
- package/dist/src/public/channels/discord/defaults.js +2 -2
- package/dist/src/public/channels/discord/discordChannel.d.ts +4 -4
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/index.d.ts +1 -1
- package/dist/src/public/channels/slack/defaults.js +4 -4
- package/dist/src/public/channels/slack/slackChannel.d.ts +4 -4
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/teams/defaults.js +3 -3
- package/dist/src/public/channels/teams/teamsChannel.d.ts +4 -4
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/telegram/defaults.js +2 -2
- package/dist/src/public/channels/telegram/telegramChannel.d.ts +4 -4
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/channels/twilio/defaults.js +2 -2
- package/dist/src/public/channels/twilio/twilioChannel.d.ts +4 -4
- package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
- package/dist/src/public/definitions/agent.d.ts +2 -1
- package/dist/src/public/definitions/defineChannel.d.ts +16 -4
- package/dist/src/public/definitions/defineChannel.js +1 -1
- package/dist/src/public/definitions/exact.d.ts +7 -0
- package/dist/src/public/definitions/exact.js +1 -0
- package/dist/src/public/definitions/hook.d.ts +2 -1
- package/dist/src/public/definitions/instructions.d.ts +2 -1
- package/dist/src/public/definitions/instrumentation.d.ts +2 -1
- package/dist/src/public/definitions/schedule.d.ts +12 -2
- package/dist/src/public/definitions/skill.d.ts +2 -1
- package/dist/src/runtime/framework-tools/skill.js +1 -1
- package/dist/src/runtime/governance/auth/types.d.ts +1 -1
- package/dist/src/sandbox/state.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ModelMessage } from "ai";
|
|
2
2
|
import type { TypedReceiveRoute } from "#channel/receive-args.js";
|
|
3
|
-
import type { SessionHandle } from "#channel/session.js";
|
|
4
3
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
5
4
|
import type { CardElement } from "#compiled/chat/index.js";
|
|
5
|
+
import type { AshCallbackContext } from "#public/definitions/callback-context.js";
|
|
6
|
+
import type { ChannelSessionOps } from "#public/definitions/defineChannel.js";
|
|
6
7
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
7
8
|
import { type SlackBotToken, type SlackHandle, type SlackThread } from "#public/channels/slack/api.js";
|
|
8
9
|
import { type SlackMessage } from "#public/channels/slack/inbound.js";
|
|
@@ -36,13 +37,12 @@ export interface SlackContext {
|
|
|
36
37
|
* `state` directly; the runtime auto-snapshots the result at step
|
|
37
38
|
* boundaries.
|
|
38
39
|
*/
|
|
39
|
-
export interface SlackEventContext extends SlackContext {
|
|
40
|
-
readonly session: SessionHandle;
|
|
40
|
+
export interface SlackEventContext extends SlackContext, ChannelSessionOps {
|
|
41
41
|
state: SlackChannelState;
|
|
42
42
|
}
|
|
43
43
|
export type { SlackApiResponse, SlackBotToken, SlackHandle, SlackThread, } from "#public/channels/slack/api.js";
|
|
44
44
|
export type { SlackWebhookVerifier } from "#public/channels/slack/verify.js";
|
|
45
|
-
type SlackEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, ctx:
|
|
45
|
+
type SlackEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, channel: SlackEventContext, ctx: AshCallbackContext) => void | Promise<void>;
|
|
46
46
|
/**
|
|
47
47
|
* JSON-serializable per-session state.
|
|
48
48
|
*
|
|
@@ -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,
|
|
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,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,modelContext:c.modelContext},{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,3 +1,3 @@
|
|
|
1
|
-
import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{splitTeamsMessageText}from"#public/channels/teams/api.js";import{renderAnsweredInputRequestMessage,renderInputRequestMessage}from"#public/channels/teams/hitl.js";function defaultTeamsAuth(e){let t=e.tenantId,n={activity_id:e.id,conversation_id:e.conversation.id,scope:e.scope,user_id:e.from.id};e.from.name!==void 0&&(n.user_name=e.from.name),e.from.aadObjectId!==void 0&&(n.aad_object_id=e.from.aadObjectId),t!==void 0&&(n.tenant_id=t),e.teamId!==void 0&&(n.team_id=e.teamId),e.teamsChannelId!==void 0&&(n.channel_id=e.teamsChannelId);let r=t?`teams:${t}:${e.from.id}`:`teams:${e.from.id}`;return{attributes:n,authenticator:`teams-activity`,issuer:t?`teams:${t}`:`teams`,principalId:r,principalType:e.from.role===`bot`?`service`:`user`,subject:e.from.aadObjectId}}async function defaultOnMessage(e,t){return t.scope!==`personal`&&!t.isBotMentioned?null:(await e.thread.startTyping(),{auth:defaultTeamsAuth(t)})}const defaultEvents={async"turn.started"(e,t){await t.thread.startTyping()},async"actions.requested"(e,t){await t.thread.startTyping()},async"input.requested"(e,t){for(let n of e.requests)await t.thread.post(renderInputRequestMessage(n,{adaptiveCardVersion:t.adaptiveCardVersion}))},async"message.completed"(e,t){if(!(e.finishReason===`tool-calls`||!e.message))for(let n of splitTeamsMessageText(e.message))await t.thread.post(n)},async"session.failed"(n,r){let
|
|
2
|
-
`))},async"turn.failed"(n,r){let
|
|
3
|
-
`))},async"authorization.required"(e,t){let
|
|
1
|
+
import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{splitTeamsMessageText}from"#public/channels/teams/api.js";import{renderAnsweredInputRequestMessage,renderInputRequestMessage}from"#public/channels/teams/hitl.js";function defaultTeamsAuth(e){let t=e.tenantId,n={activity_id:e.id,conversation_id:e.conversation.id,scope:e.scope,user_id:e.from.id};e.from.name!==void 0&&(n.user_name=e.from.name),e.from.aadObjectId!==void 0&&(n.aad_object_id=e.from.aadObjectId),t!==void 0&&(n.tenant_id=t),e.teamId!==void 0&&(n.team_id=e.teamId),e.teamsChannelId!==void 0&&(n.channel_id=e.teamsChannelId);let r=t?`teams:${t}:${e.from.id}`:`teams:${e.from.id}`;return{attributes:n,authenticator:`teams-activity`,issuer:t?`teams:${t}`:`teams`,principalId:r,principalType:e.from.role===`bot`?`service`:`user`,subject:e.from.aadObjectId}}async function defaultOnMessage(e,t){return t.scope!==`personal`&&!t.isBotMentioned?null:(await e.thread.startTyping(),{auth:defaultTeamsAuth(t)})}const defaultEvents={async"turn.started"(e,t,n){await t.thread.startTyping()},async"actions.requested"(e,t,n){await t.thread.startTyping()},async"input.requested"(e,t,n){for(let n of e.requests)await t.thread.post(renderInputRequestMessage(n,{adaptiveCardVersion:t.adaptiveCardVersion}))},async"message.completed"(e,t,n){if(!(e.finishReason===`tool-calls`||!e.message))for(let n of splitTeamsMessageText(e.message))await t.thread.post(n)},async"session.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.thread.post([`This session could not recover from an error${a}.`,``,`Start a new Teams conversation to continue.`,...o?[``,`Error id: ${o}`]:[]].join(`
|
|
2
|
+
`))},async"turn.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.thread.post([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...o?[``,`Error id: ${o}`]:[]].join(`
|
|
3
|
+
`))},async"authorization.required"(e,t,r){let i=formatConnectionDisplayName(e.name),a=e.authorization?.url,o=a?`Authorization required for ${i}: ${a}`:`Authorization required for ${i}.`,s=await t.thread.post({attachments:[{content:parseJsonObject({$schema:`http://adaptivecards.io/schemas/adaptive-card.json`,actions:a?[{title:`Sign in with ${i}`,type:`Action.OpenUrl`,url:a}]:[],body:[{text:`Authorization required for ${i}`,type:`TextBlock`,weight:`Bolder`,wrap:!0},{text:t.state.triggeringUser?`Requested by ${t.state.triggeringUser.name??t.state.triggeringUser.id}.`:`No triggering user is available for a private prompt.`,type:`TextBlock`,wrap:!0}],type:`AdaptiveCard`,version:t.adaptiveCardVersion}),contentType:`application/vnd.microsoft.card.adaptive`}],text:o});s.id&&(t.state.pendingAuthActivityId=s.id)},async"authorization.completed"(e,t,n){let r=t.state.pendingAuthActivityId;if(!r)return;let a=buildAuthCompletedText({displayName:formatConnectionDisplayName(e.name),outcome:e.outcome,reason:e.reason});await t.thread.update(r,renderAnsweredInputRequestMessage({prompt:a})),t.state.pendingAuthActivityId=null}};function formatConnectionDisplayName(e){return e.length===0?e:e.charAt(0).toUpperCase()+e.slice(1)}function buildAuthCompletedText(e){if(e.outcome===`authorized`)return`${e.displayName} connected.`;let t=e.reason===void 0?``:` (${e.reason})`;return`${e.displayName} authorization ${e.outcome}${t}.`}function teamsMentionUser(e){let t=e.name??e.id;return{mentioned:{id:e.id,name:e.name},text:`<at>${t}</at>`,type:`mention`}}export{buildAuthCompletedText,defaultEvents,defaultOnMessage,defaultTeamsAuth,formatConnectionDisplayName,teamsMentionUser};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ModelMessage } from "ai";
|
|
2
2
|
import type { TypedReceiveRoute } from "#channel/receive-args.js";
|
|
3
|
-
import type { SessionHandle } from "#channel/session.js";
|
|
4
3
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
4
|
+
import type { AshCallbackContext } from "#public/definitions/callback-context.js";
|
|
5
|
+
import type { ChannelSessionOps } from "#public/definitions/defineChannel.js";
|
|
5
6
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
6
7
|
import { type TeamsFilesConfig } from "#public/channels/teams/attachments.js";
|
|
7
8
|
import { type TeamsApiOptions, type TeamsApiResponse, type TeamsChannelAccount, type TeamsCredentials, type TeamsMention, type TeamsMessageBody, type TeamsOutboundActivity, type TeamsPostedActivity } from "#public/channels/teams/api.js";
|
|
@@ -20,9 +21,8 @@ export interface TeamsContext {
|
|
|
20
21
|
readonly thread: TeamsThread;
|
|
21
22
|
}
|
|
22
23
|
/** Event-handler Teams context, including mutable per-conversation state. */
|
|
23
|
-
export interface TeamsEventContext extends TeamsContext {
|
|
24
|
+
export interface TeamsEventContext extends TeamsContext, ChannelSessionOps {
|
|
24
25
|
readonly adaptiveCardVersion: string;
|
|
25
|
-
readonly session: SessionHandle;
|
|
26
26
|
state: TeamsChannelState;
|
|
27
27
|
}
|
|
28
28
|
/** JSON-serializable Teams channel state. */
|
|
@@ -78,7 +78,7 @@ export type TeamsInboundResultOrPromise = TeamsInboundResult | Promise<TeamsInbo
|
|
|
78
78
|
export type TeamsInvokeResult = Record<string, unknown> | Response | null | undefined;
|
|
79
79
|
/** Sync or async {@link TeamsInvokeResult}. */
|
|
80
80
|
export type TeamsInvokeResultOrPromise = TeamsInvokeResult | Promise<TeamsInvokeResult>;
|
|
81
|
-
type TeamsEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, ctx:
|
|
81
|
+
type TeamsEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, channel: TeamsEventContext, ctx: AshCallbackContext) => void | Promise<void>;
|
|
82
82
|
/** Event handlers supported by `teamsChannel({ events })`. */
|
|
83
83
|
export interface TeamsChannelEvents {
|
|
84
84
|
readonly "turn.started"?: TeamsEventHandler<"turn.started">;
|
|
@@ -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`,
|
|
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,modelContext:r.modelContext},{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,3 +1,3 @@
|
|
|
1
|
-
import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{registerTelegramFreeformPrompt,renderTelegramInputRequest}from"#public/channels/telegram/hitl.js";function defaultTelegramAuth(e){let t=e.from;if(!t)return null;let n={chat_id:e.chat.id,chat_type:e.chat.type,message_id:e.messageId,user_id:t.id};e.chat.title!==void 0&&(n.chat_title=e.chat.title),e.messageThreadId!==void 0&&(n.message_thread_id=String(e.messageThreadId)),t.username!==void 0&&(n.username=t.username);let r=e.chat.type===`group`||e.chat.type===`supergroup`,i=r?`telegram:${e.chat.id}:${t.id}`:`telegram:${t.id}`;return{attributes:n,authenticator:`telegram-webhook`,issuer:r?`telegram:${e.chat.id}`:`telegram`,principalId:i,principalType:t.isBot?`service`:`user`}}async function defaultOnMessage(e,t){return shouldDispatchTelegramMessage(t,e.telegram.botUsername)?(await e.telegram.startTyping(),{auth:defaultTelegramAuth(t)}):null}const defaultEvents={async"turn.started"(e,t){await t.telegram.startTyping()},async"actions.requested"(e,t){await t.telegram.startTyping()},async"input.requested"(e,t){for(let i of e.requests){let e=renderTelegramInputRequest(i,t.state),a=await t.telegram.post({reply_markup:e.replyMarkup,text:e.text});e.freeformRequestId!==void 0&&a.id&®isterTelegramFreeformPrompt(t.state,{messageId:a.id,requestId:e.freeformRequestId})}},async"message.completed"(e,t){e.finishReason===`tool-calls`||!e.message||await t.telegram.post(e.message)},async"turn.failed"(n,r){let
|
|
2
|
-
`))},async"session.failed"(n,r){let
|
|
1
|
+
import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{registerTelegramFreeformPrompt,renderTelegramInputRequest}from"#public/channels/telegram/hitl.js";function defaultTelegramAuth(e){let t=e.from;if(!t)return null;let n={chat_id:e.chat.id,chat_type:e.chat.type,message_id:e.messageId,user_id:t.id};e.chat.title!==void 0&&(n.chat_title=e.chat.title),e.messageThreadId!==void 0&&(n.message_thread_id=String(e.messageThreadId)),t.username!==void 0&&(n.username=t.username);let r=e.chat.type===`group`||e.chat.type===`supergroup`,i=r?`telegram:${e.chat.id}:${t.id}`:`telegram:${t.id}`;return{attributes:n,authenticator:`telegram-webhook`,issuer:r?`telegram:${e.chat.id}`:`telegram`,principalId:i,principalType:t.isBot?`service`:`user`}}async function defaultOnMessage(e,t){return shouldDispatchTelegramMessage(t,e.telegram.botUsername)?(await e.telegram.startTyping(),{auth:defaultTelegramAuth(t)}):null}const defaultEvents={async"turn.started"(e,t,n){await t.telegram.startTyping()},async"actions.requested"(e,t,n){await t.telegram.startTyping()},async"input.requested"(e,t,i){for(let i of e.requests){let e=renderTelegramInputRequest(i,t.state),a=await t.telegram.post({reply_markup:e.replyMarkup,text:e.text});e.freeformRequestId!==void 0&&a.id&®isterTelegramFreeformPrompt(t.state,{messageId:a.id,requestId:e.freeformRequestId})}},async"message.completed"(e,t,n){e.finishReason===`tool-calls`||!e.message||await t.telegram.post(e.message)},async"turn.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.telegram.post([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...o?[``,`Error id: ${o}`]:[]].join(`
|
|
2
|
+
`))},async"session.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.telegram.post([`This session could not recover from an error${a}.`,``,`Start a new message to continue.`,...o?[``,`Error id: ${o}`]:[]].join(`
|
|
3
3
|
`))}};function shouldDispatchTelegramMessage(e,t){if(e.from?.isBot===!0||e.chat.type===`channel`)return!1;let n=e.text||e.caption;return n.trim().length>0||e.attachments.length>0?!!(e.chat.type===`private`||e.replyToMessage?.from?.isBot===!0||isBotCommand(n,t)||t!==void 0&&mentionsBot(n,t)):!1}function isBotCommand(e,t){let n=/^\/(?<command>[A-Za-z0-9_]+)(?:@(?<target>[A-Za-z0-9_]+))?(?:\s|$)/u.exec(e);if(!n)return!1;let r=n.groups?.target;return r===void 0?!0:t!==void 0&&r.toLowerCase()===t.toLowerCase()}function mentionsBot(e,t){return e.toLowerCase().includes(`@${t.toLowerCase()}`)}export{defaultEvents,defaultOnMessage,defaultTelegramAuth};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ModelMessage } from "ai";
|
|
2
2
|
import type { TypedReceiveRoute } from "#channel/receive-args.js";
|
|
3
|
-
import type { SessionHandle } from "#channel/session.js";
|
|
4
3
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
4
|
+
import type { AshCallbackContext } from "#public/definitions/callback-context.js";
|
|
5
|
+
import type { ChannelSessionOps } from "#public/definitions/defineChannel.js";
|
|
5
6
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
6
7
|
import { type TelegramApiOptions, type TelegramApiResponse, type TelegramCredentials, type TelegramMessageBody, type TelegramMessageResult } from "#public/channels/telegram/api.js";
|
|
7
8
|
import { type TelegramHitlState } from "#public/channels/telegram/hitl.js";
|
|
@@ -20,8 +21,7 @@ export interface TelegramContext {
|
|
|
20
21
|
readonly telegram: TelegramHandle;
|
|
21
22
|
}
|
|
22
23
|
/** Event-handler Telegram context, including mutable per-conversation state. */
|
|
23
|
-
export interface TelegramEventContext extends TelegramContext {
|
|
24
|
-
readonly session: SessionHandle;
|
|
24
|
+
export interface TelegramEventContext extends TelegramContext, ChannelSessionOps {
|
|
25
25
|
state: TelegramChannelState;
|
|
26
26
|
}
|
|
27
27
|
/** JSON-serializable Telegram channel state. */
|
|
@@ -64,7 +64,7 @@ export type TelegramInboundResult = {
|
|
|
64
64
|
} | null;
|
|
65
65
|
/** Sync or async {@link TelegramInboundResult}. */
|
|
66
66
|
export type TelegramInboundResultOrPromise = TelegramInboundResult | Promise<TelegramInboundResult>;
|
|
67
|
-
type TelegramEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, ctx:
|
|
67
|
+
type TelegramEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, channel: TelegramEventContext, ctx: AshCallbackContext) => void | Promise<void>;
|
|
68
68
|
/** Event handlers supported by `telegramChannel({ events })`. */
|
|
69
69
|
export interface TelegramChannelEvents {
|
|
70
70
|
readonly "turn.started"?: TelegramEventHandler<"turn.started">;
|
|
@@ -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{
|
|
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,3 +1,3 @@
|
|
|
1
|
-
import{extractErrorId,formatErrorHint}from"#internal/logging.js";function defaultTwilioAuth(e){let t={channel:e.channel,from:e.from};return e.to!==void 0&&(t.to=e.to),{attributes:t,authenticator:`twilio-webhook`,issuer:`twilio`,principalId:`twilio:${e.from}`,principalType:`user`}}function defaultOnText(e,t){return{auth:defaultTwilioAuth({channel:`text`,from:t.from,to:t.to})}}function defaultOnVoice(e,t){return{}}function defaultOnVoiceTranscription(e,t){return{auth:defaultTwilioAuth({channel:`voice`,from:t.from,to:t.to})}}const defaultEvents={async"message.completed"(e,t){e.finishReason===`tool-calls`||!e.message||await t.twilio.sendMessage(e.message)},async"turn.failed"(n,r){let
|
|
2
|
-
`))},async"session.failed"(n,r){let
|
|
1
|
+
import{extractErrorId,formatErrorHint}from"#internal/logging.js";function defaultTwilioAuth(e){let t={channel:e.channel,from:e.from};return e.to!==void 0&&(t.to=e.to),{attributes:t,authenticator:`twilio-webhook`,issuer:`twilio`,principalId:`twilio:${e.from}`,principalType:`user`}}function defaultOnText(e,t){return{auth:defaultTwilioAuth({channel:`text`,from:t.from,to:t.to})}}function defaultOnVoice(e,t){return{}}function defaultOnVoiceTranscription(e,t){return{auth:defaultTwilioAuth({channel:`voice`,from:t.from,to:t.to})}}const defaultEvents={async"message.completed"(e,t,n){e.finishReason===`tool-calls`||!e.message||await t.twilio.sendMessage(e.message)},async"turn.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.twilio.sendMessage([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...o?[``,`Error id: ${o}`]:[]].join(`
|
|
2
|
+
`))},async"session.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.twilio.sendMessage([`This session could not recover from an error${a}.`,``,`Start a new message to continue.`,...o?[``,`Error id: ${o}`]:[]].join(`
|
|
3
3
|
`))}};export{defaultEvents,defaultOnText,defaultOnVoice,defaultOnVoiceTranscription,defaultTwilioAuth};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TypedReceiveRoute } from "#channel/receive-args.js";
|
|
2
|
-
import type { SessionHandle } from "#channel/session.js";
|
|
3
2
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
3
|
+
import type { AshCallbackContext } from "#public/definitions/callback-context.js";
|
|
4
|
+
import type { ChannelSessionOps } from "#public/definitions/defineChannel.js";
|
|
4
5
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
5
6
|
import { type TwilioApiOptions, type TwilioApiResponse, type TwilioCredentials } from "#public/channels/twilio/api.js";
|
|
6
7
|
import { type TwilioTextMessage, type TwilioVoiceCall, type TwilioVoiceTranscription } from "#public/channels/twilio/inbound.js";
|
|
@@ -16,8 +17,7 @@ export interface TwilioContext {
|
|
|
16
17
|
readonly twilio: TwilioHandle;
|
|
17
18
|
}
|
|
18
19
|
/** Event-handler Twilio context, including mutable per-phone channel state. */
|
|
19
|
-
export interface TwilioEventContext extends TwilioContext {
|
|
20
|
-
readonly session: SessionHandle;
|
|
20
|
+
export interface TwilioEventContext extends TwilioContext, ChannelSessionOps {
|
|
21
21
|
state: TwilioChannelState;
|
|
22
22
|
}
|
|
23
23
|
/** JSON-serializable state for the phone-number conversation. */
|
|
@@ -73,7 +73,7 @@ export interface TwilioVoiceResult {
|
|
|
73
73
|
}
|
|
74
74
|
/** Sync or async {@link TwilioVoiceResult}. */
|
|
75
75
|
export type TwilioVoiceResultOrPromise = TwilioVoiceResult | null | undefined | Promise<TwilioVoiceResult | null | undefined>;
|
|
76
|
-
type TwilioEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, ctx:
|
|
76
|
+
type TwilioEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, channel: TwilioEventContext, ctx: AshCallbackContext) => void | Promise<void>;
|
|
77
77
|
/** Event handlers supported by `twilioChannel({ events })`. */
|
|
78
78
|
export interface TwilioChannelEvents {
|
|
79
79
|
readonly "turn.started"?: TwilioEventHandler<"turn.started">;
|
|
@@ -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{
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import type { PublicAgentDefinition } from "#shared/agent-definition.js";
|
|
2
|
+
import type { ExactDefinition } from "#public/definitions/exact.js";
|
|
2
3
|
export type { AgentModelOptionsDefinition, AgentBuildDefinition, PublicAgentModelDefinition as AgentModelDefinition, PublicAgentCompactionDefinition as AgentCompactionDefinition, } from "#shared/agent-definition.js";
|
|
3
4
|
/**
|
|
4
5
|
* Additive public agent configuration authored in `agent.ts`.
|
|
@@ -16,4 +17,4 @@ export type AgentDefinition = PublicAgentDefinition;
|
|
|
16
17
|
/**
|
|
17
18
|
* Defines additive agent configuration.
|
|
18
19
|
*/
|
|
19
|
-
export declare function defineAgent<TAgent extends AgentDefinition>(definition: TAgent): TAgent;
|
|
20
|
+
export declare function defineAgent<TAgent extends AgentDefinition>(definition: ExactDefinition<TAgent, AgentDefinition>): TAgent;
|
|
@@ -2,6 +2,7 @@ import type { FetchFileResult } from "#channel/adapter.js";
|
|
|
2
2
|
import { CHANNEL_SENTINEL } from "#channel/compiled-channel.js";
|
|
3
3
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
4
4
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
5
|
+
import type { AshCallbackContext } from "#public/definitions/callback-context.js";
|
|
5
6
|
import type { RouteDefinition, SendFn } from "#channel/routes.js";
|
|
6
7
|
import type { Session, SessionHandle } from "#channel/session.js";
|
|
7
8
|
export type { Session, SessionHandle } from "#channel/session.js";
|
|
@@ -12,9 +13,15 @@ type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessa
|
|
|
12
13
|
}> extends {
|
|
13
14
|
data: infer D;
|
|
14
15
|
} ? D : undefined;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Channel-specific session operations available on the `channel` argument
|
|
18
|
+
* of every channel event handler.
|
|
19
|
+
*/
|
|
20
|
+
export interface ChannelSessionOps {
|
|
21
|
+
readonly continuationToken: string;
|
|
22
|
+
setContinuationToken(token: string): void;
|
|
23
|
+
}
|
|
24
|
+
type ChannelEventHandler<T extends HandleMessageStreamEvent["type"], TCtx> = (data: EventData<T>, channel: TCtx & ChannelSessionOps, ctx: AshCallbackContext) => void | Promise<void>;
|
|
18
25
|
export interface ChannelEvents<TCtx = void> {
|
|
19
26
|
readonly "turn.started"?: ChannelEventHandler<"turn.started", TCtx>;
|
|
20
27
|
readonly "actions.requested"?: ChannelEventHandler<"actions.requested", TCtx>;
|
|
@@ -39,8 +46,13 @@ export interface ChannelConfig<TState = undefined, TCtx = void> {
|
|
|
39
46
|
* `onThreadTsChanged` calling `session.setContinuationToken(...)`) can close
|
|
40
47
|
* over it. State mutations made inside the returned context flow
|
|
41
48
|
* back through `adapter.state` automatically.
|
|
49
|
+
*
|
|
50
|
+
* Return the channel-owned context (thread handles, API clients, etc.).
|
|
51
|
+
* The framework passes this as the `channel` argument to event handlers
|
|
52
|
+
* (with {@link ChannelSessionOps} injected) and passes
|
|
53
|
+
* {@link AshCallbackContext} as a separate `ctx` argument.
|
|
42
54
|
*/
|
|
43
|
-
context?(state: NonNullable<TState>, session: SessionHandle): TCtx
|
|
55
|
+
context?(state: NonNullable<TState>, session: SessionHandle): Omit<TCtx, keyof ChannelSessionOps>;
|
|
44
56
|
readonly routes: readonly RouteDefinition<TState>[];
|
|
45
57
|
receive?(input: {
|
|
46
58
|
readonly message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CHANNEL_SENTINEL}from"#channel/compiled-channel.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
|
|
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=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 t=d?.[e];t&&(l=!0,c[e]=(e,r)=>{let i={...r,continuationToken:r.session?.continuationToken??``,setContinuationToken:e=>r.session?.setContinuationToken(e)},a;try{a=buildCallbackContext()}catch{}return t(e,i,a)})}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};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps definition helpers from accepting authored keys outside the public
|
|
3
|
+
* definition shape while preserving literal inference for valid inputs.
|
|
4
|
+
*/
|
|
5
|
+
export type ExactDefinition<TInput, TShape> = TInput & {
|
|
6
|
+
readonly [TKey in Exclude<keyof TInput, keyof TShape>]: never;
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ModelMessage } from "ai";
|
|
2
2
|
import type { HandleMessageStreamEvent } from "../../protocol/message.js";
|
|
3
3
|
import type { AshCallbackContext } from "./callback-context.js";
|
|
4
|
+
import type { ExactDefinition } from "./exact.js";
|
|
4
5
|
import type { NamedSkillDefinition } from "./skill.js";
|
|
5
6
|
/**
|
|
6
7
|
* Context passed to every hook handler.
|
|
@@ -91,4 +92,4 @@ export interface HookDefinition {
|
|
|
91
92
|
* `defineHook({ lifecycle: { session, turn }, events: { "session.started": … } })`
|
|
92
93
|
* and receive a typed {@link HookDefinition}.
|
|
93
94
|
*/
|
|
94
|
-
export declare function defineHook<T extends HookDefinition>(definition: T): T;
|
|
95
|
+
export declare function defineHook<T extends HookDefinition>(definition: ExactDefinition<T, HookDefinition>): T;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ExactDefinition } from "#public/definitions/exact.js";
|
|
1
2
|
/**
|
|
2
3
|
* Public definition for an instructions prompt authored in markdown or
|
|
3
4
|
* TypeScript.
|
|
@@ -15,4 +16,4 @@ export interface InstructionsDefinition {
|
|
|
15
16
|
* Defines an instructions prompt in TypeScript using the same shape
|
|
16
17
|
* discovery produces from markdown.
|
|
17
18
|
*/
|
|
18
|
-
export declare function defineInstructions<TInstructions extends InstructionsDefinition>(definition: TInstructions): TInstructions;
|
|
19
|
+
export declare function defineInstructions<TInstructions extends InstructionsDefinition>(definition: ExactDefinition<TInstructions, InstructionsDefinition>): TInstructions;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ExactDefinition } from "#public/definitions/exact.js";
|
|
1
2
|
/**
|
|
2
3
|
* Context passed to the {@link InstrumentationDefinition.setup} callback.
|
|
3
4
|
*/
|
|
@@ -62,4 +63,4 @@ export interface InstrumentationDefinition {
|
|
|
62
63
|
* The `setup` callback is invoked later by the framework with the resolved
|
|
63
64
|
* agent name — it is not called during `defineInstrumentation` itself.
|
|
64
65
|
*/
|
|
65
|
-
export declare function defineInstrumentation<T extends InstrumentationDefinition>(definition: T): T;
|
|
66
|
+
export declare function defineInstrumentation<T extends InstrumentationDefinition>(definition: ExactDefinition<T, InstrumentationDefinition>): T;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CrossChannelReceiveFn } from "#channel/cross-channel-receive.js";
|
|
2
2
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
3
|
+
import type { ExactDefinition } from "#public/definitions/exact.js";
|
|
3
4
|
export type { InferReceiveArgs, TypedReceiveRoute } from "#channel/receive-args.js";
|
|
4
5
|
/**
|
|
5
6
|
* Arguments handed to {@link ScheduleDefinition.run}. Tight subset of
|
|
@@ -43,11 +44,20 @@ export type ScheduleRunHandler = (args: ScheduleHandlerArgs) => Promise<void> |
|
|
|
43
44
|
* Identity is derived from the file path under `agent/schedules/`;
|
|
44
45
|
* authored definitions do not carry a `name` field.
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
+
interface ScheduleDefinitionFields {
|
|
47
48
|
readonly cron: string;
|
|
48
49
|
readonly markdown?: string;
|
|
49
50
|
readonly run?: ScheduleRunHandler;
|
|
50
51
|
}
|
|
52
|
+
export type ScheduleDefinition = {
|
|
53
|
+
readonly cron: string;
|
|
54
|
+
readonly markdown: string;
|
|
55
|
+
readonly run?: never;
|
|
56
|
+
} | {
|
|
57
|
+
readonly cron: string;
|
|
58
|
+
readonly markdown?: never;
|
|
59
|
+
readonly run: ScheduleRunHandler;
|
|
60
|
+
};
|
|
51
61
|
/**
|
|
52
62
|
* Defines a schedule in TypeScript. Export as the default from
|
|
53
63
|
* `agent/schedules/<name>.ts`.
|
|
@@ -77,4 +87,4 @@ export interface ScheduleDefinition {
|
|
|
77
87
|
* });
|
|
78
88
|
* ```
|
|
79
89
|
*/
|
|
80
|
-
export declare function defineSchedule<TSchedule extends ScheduleDefinition>(definition: TSchedule): TSchedule;
|
|
90
|
+
export declare function defineSchedule<TSchedule extends ScheduleDefinition>(definition: ExactDefinition<TSchedule, ScheduleDefinitionFields>): TSchedule;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NamedSkillDefinition, SkillFileContent, SkillPackageDefinition } from "#shared/skill-definition.js";
|
|
2
|
+
import type { ExactDefinition } from "#public/definitions/exact.js";
|
|
2
3
|
export type { NamedSkillDefinition, SkillFileContent, SkillPackageDefinition };
|
|
3
4
|
/**
|
|
4
5
|
* Public definition for skill instructions lowered from authored markdown
|
|
@@ -12,4 +13,4 @@ export type SkillDefinition = SkillPackageDefinition;
|
|
|
12
13
|
* Defines a skill in TypeScript using the same shape discovery produces from
|
|
13
14
|
* markdown, with optional package-relative sibling files.
|
|
14
15
|
*/
|
|
15
|
-
export declare function defineSkill<TSkill extends SkillDefinition>(definition: TSkill): TSkill;
|
|
16
|
+
export declare function defineSkill<TSkill extends SkillDefinition>(definition: ExactDefinition<TSkill, SkillDefinition>): TSkill;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SandboxKey}from"#context/keys.js";import{
|
|
1
|
+
import{SandboxKey}from"#context/keys.js";import{loadContext}from"#context/container.js";import{loadSkillFromSandbox}from"#runtime/skills/sandbox-access.js";async function executeLoadSkillTool(t){let n=loadContext().get(SandboxKey);if(n===void 0)throw Error(`The load_skill tool requires sandbox access on the runtime context. Ensure the step is running inside a managed runtime context with sandbox support.`);let{skill:r}=t;return await loadSkillFromSandbox(n,r)}const SKILL_TOOL_DEFINITION={description:[`Load the full instructions for one available skill by name or id.`,`Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill.`,`Loading adds the skill instructions to the current turn.`,`Choose the "skill" value from the Available skills block.`].join(` `),execute:e=>executeLoadSkillTool(e),inputSchema:{additionalProperties:!1,properties:{skill:{description:`Available skill name or id.`,type:`string`}},required:[`skill`],type:`object`},logicalPath:`ash:framework/load-skill`,name:`load_skill`,retentionPolicy:`keep`,sourceId:`ash:load-skill-tool`,sourceKind:`module`};export{SKILL_TOOL_DEFINITION};
|
|
@@ -99,6 +99,6 @@ export type RouteStrategyAuthenticationResult = {
|
|
|
99
99
|
};
|
|
100
100
|
/**
|
|
101
101
|
* Creates the session-auth projection persisted on runtime state and exposed to
|
|
102
|
-
* authored `
|
|
102
|
+
* authored code via `ctx.session.auth`.
|
|
103
103
|
*/
|
|
104
104
|
export declare function createRuntimeSessionAuthContext(principal: AuthenticatedCallerPrincipal): RuntimeSessionAuthContext;
|
|
@@ -24,7 +24,7 @@ export interface SandboxState {
|
|
|
24
24
|
* Lazy sandbox accessor bound to one step execution.
|
|
25
25
|
*
|
|
26
26
|
* Returned by `ensureSandboxAccess` and placed on the `AshContext` (via
|
|
27
|
-
* `SandboxKey`) so tools can call `getSandbox()`.
|
|
27
|
+
* `SandboxKey`) so tools can call `ctx.getSandbox()`.
|
|
28
28
|
*/
|
|
29
29
|
export interface SandboxAccess {
|
|
30
30
|
captureState(): Promise<SandboxState>;
|