eve 0.44.2 → 0.44.4
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 +21 -0
- package/dist/src/channel/adapter.d.ts +2 -2
- package/dist/src/chunks/{use-eve-agent-DpUEwS23.js → use-eve-agent-BJwaMe5k.js} +6 -2
- package/dist/src/chunks/{use-eve-agent-BsLJiIbc.js → use-eve-agent-BtsOXv3s.js} +6 -2
- package/dist/src/cli/dev/tui/prompt-command-handler.js +1 -1
- package/dist/src/cli/dev/tui/prompt-commands.d.ts +1 -1
- package/dist/src/cli/dev/tui/prompt-commands.js +1 -1
- package/dist/src/cli/dev/tui/setup-commands.d.ts +2 -0
- package/dist/src/cli/dev/tui/setup-commands.js +1 -1
- package/dist/src/client/eve-agent-store.js +1 -1
- package/dist/src/compiler/extension-compatibility.d.ts +2 -2
- package/dist/src/compiler/extension-compatibility.js +1 -1
- package/dist/src/execution/sandbox/bindings/just-bash-runtime.d.ts +1 -0
- package/dist/src/execution/sandbox/bindings/just-bash-runtime.js +1 -1
- package/dist/src/execution/sandbox/bindings/just-bash.js +1 -1
- package/dist/src/execution/web-fetch/request.js +1 -1
- package/dist/src/execution/web-fetch/tool.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/harness/attachment-staging.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/public/channels/chat-sdk/authorization.d.ts +3 -0
- package/dist/src/public/channels/chat-sdk/authorization.js +3 -0
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.d.ts +2 -0
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +2 -2
- package/dist/src/public/channels/eve.js +3 -3
- package/dist/src/public/channels/linq/index.d.ts +1 -1
- package/dist/src/public/channels/linq/index.js +1 -1
- package/dist/src/public/channels/linq/linqChannel.d.ts +3 -1
- package/dist/src/public/channels/linq/linqChannel.js +1 -1
- package/dist/src/public/channels/photon/index.d.ts +1 -1
- package/dist/src/public/channels/photon/index.js +1 -1
- package/dist/src/public/channels/photon/photonIMessageChannel.d.ts +3 -1
- package/dist/src/public/channels/photon/photonIMessageChannel.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +15 -5
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/attachments.d.ts +2 -2
- package/dist/src/public/channels/slack/attachments.js +1 -1
- package/dist/src/public/channels/slack/hitl.d.ts +2 -0
- package/dist/src/public/channels/slack/inbound-content.d.ts +4 -0
- package/dist/src/public/channels/slack/inbound-content.js +4 -0
- package/dist/src/public/channels/slack/inbound.d.ts +8 -1
- package/dist/src/public/channels/slack/inbound.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +1 -1
- package/dist/src/public/channels/slack/input-response.d.ts +1 -0
- package/dist/src/public/channels/slack/input-response.js +1 -1
- package/dist/src/public/channels/slack/interaction-cards.d.ts +10 -0
- package/dist/src/public/channels/slack/interaction-cards.js +1 -0
- package/dist/src/public/channels/slack/interactions.d.ts +1 -0
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +6 -1
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/sandbox/just-bash-sandbox.d.ts +12 -1
- package/dist/src/setup/flows/registry.d.ts +7 -1
- package/dist/src/setup/flows/registry.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +39 -2
- package/dist/src/setup/scaffold/create/web-template.d.ts +4 -4
- package/dist/src/setup/scaffold/create/web-template.js +1 -1
- package/dist/src/shared/channel-definition.d.ts +5 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/channels/linq.mdx +1 -1
- package/docs/channels/photon.mdx +4 -4
- package/docs/channels/slack.mdx +18 -3
- package/docs/concepts/built-in-tools.md +1 -0
- package/docs/getting-started.mdx +1 -0
- package/docs/guides/dev-tui.md +26 -15
- package/docs/meta.json +1 -1
- package/docs/sandbox.mdx +20 -0
- package/package.json +2 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{isRuntimeSessionOwnershipConflictError}from"#execution/runtime-errors.js";import{isInputResponse}from"#runtime/input/types.js";import{hasInternalRefScheme}from"#internal/attachments/url-refs.js";import{EVE_INFO_ROUTE_PATH,EVE_SESSION_CANCEL_ROUTE_PATTERN,EVE_SESSION_CLEAR_ROUTE_PATTERN,EVE_SESSION_COMPACT_ROUTE_PATTERN,EVE_SESSION_RESET_ROUTE_PATTERN,EVE_SESSION_ROUTE_PATH,EVE_SESSION_ROUTE_PATTERN,EVE_SESSION_STREAM_ROUTE_PATTERN,EVE_SUBAGENT_STREAM_ROUTE_PATTERN,createEveSessionStreamRoutePath,createEveSubagentStreamRoutePath}from"#protocol/routes.js";import{EVE_MESSAGE_STREAM_CONTENT_TYPE,EVE_MESSAGE_STREAM_FORMAT,EVE_MESSAGE_STREAM_VERSION,EVE_SESSION_ID_HEADER,EVE_STREAM_FORMAT_HEADER,EVE_STREAM_TAIL_INDEX_HEADER,EVE_STREAM_VERSION_HEADER}from"#protocol/message.js";import{parseJsonObject}from"#shared/json.js";import{GET,POST,defineChannel}from"#public/definitions/channel.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{parseTraceparent}from"#protocol/traceparent.js";import{routeAuth}from"#public/channels/auth.js";import{readAgentInfoRouteResponse,readRemoteAgentStreamHeadersResolver,readRouteSessionCreator}from"#internal/nitro/routes/channel-route-context.js";import{resolveForwardedPrincipal}from"#channel/forwarded-principal.js";import{collectUploadPolicyViolations,formatUploadPolicyViolation,mergeUploadPolicy}from"#public/channels/upload-policy.js";const log=createLogger(`eve.channel`);function defaultEveAuth(e){return e.eve.caller}function eveChannel(e){let r=mergeUploadPolicy(e.uploadPolicy);return defineChannel({cors:normalizeEveCors(e.cors),turnPolicy:e.turnPolicy,routes:[GET(EVE_INFO_ROUTE_PATH,async(t,n)=>{let r=await routeAuth(t,e.auth);if(r instanceof Response)return r;let i=readAgentInfoRouteResponse(n);return i===void 0?Response.json({error:`Agent info route requires internal channel dispatch context.`,ok:!1},{status:500}):await i()}),POST(EVE_SESSION_ROUTE_PATH,async(i,a)=>{let o=await routeAuth(i,e.auth);if(o instanceof Response)return o;let s=await parseJsonRequest(i);if(s instanceof Response)return s;let c=rejectSessionContinuationToken(s);if(c!==null)return c;let l=await resolveForwardedPrincipal({trustedForwarders:e.trustedForwarders,forwarder:o,payload:s});if(l instanceof Response)return l;let u=parseCreateBody(s);if(u instanceof Response)return u;let d=u.callback===void 0?void 0:parseTraceparent(i.headers.get(`traceparent`)),f=checkUploadPolicy(u,r);if(f!==null)return f;if(u.operationId!==void 0&&l.auth.principalType===`anonymous`)return Response.json({error:`operationId requires an authenticated principal.`,ok:!1},{status:400});let p=u.operationId===void 0?void 0:await deriveOperationContinuationToken({auth:l.auth,operationId:u.operationId});if(p!==void 0){let e=await a.resolveSession(p);if(e!==void 0)return Response.json({ok:!0,sessionId:e.id,status:`accepted`},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:e.id},status:202})}let m=await resolveOnMessage({auth:l.auth,config:e,message:u.message,request:i});if(m instanceof Response)return m;let h=readRouteSessionCreator(a);if(h===void 0)return Response.json({error:`Session creation requires internal channel dispatch context.`,ok:!1},{status:500});let g;try{g=await h({auth:m.auth,capabilities:u.capabilities??(u.mode===`task`?void 0:{requestInput:!0}),callback:u.callback,continuationToken:p,initiatorAuth:l.accepted?l.initiatorAuth:void 0,input:{message:u.message,context:mergeContext(u.context,m.context),outputSchema:u.outputSchema},mode:u.mode??`conversation`,parentTraceContext:d,title:m.title})}catch(e){if(p!==void 0&&isRuntimeSessionOwnershipConflictError(e))return Response.json({ok:!0,sessionId:e.ownerSessionId,status:`accepted`},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:e.ownerSessionId},status:202});let r=logError(log,`session-create request failed`,e);return Response.json({error:`Failed to create the session.`,errorId:r,ok:!1},{status:500})}return Response.json({ok:!0,sessionId:g.sessionId,status:`accepted`},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:g.sessionId},status:202})}),POST(EVE_SESSION_ROUTE_PATTERN,async(n,{attachSession:i,params:a})=>{let o=await routeAuth(n,e.auth);if(o instanceof Response)return o;let s=requireSessionId(a);if(s instanceof Response)return s;let c=await parseJsonRequest(n);if(c instanceof Response)return c;let l=await resolveForwardedPrincipal({trustedForwarders:e.trustedForwarders,forwarder:o,payload:c});if(l instanceof Response)return l;let u=parseSessionMessageBody(c);if(u instanceof Response)return u;let d=checkUploadPolicy(u,r);if(d!==null)return d;let f=u.context,p=l.auth;if(u.message!==void 0){let t=await resolveOnMessage({auth:l.auth,config:e,message:u.message,request:n,sessionId:s});if(t instanceof Response)return t;f=mergeContext(u.context,t.context),p=t.auth}let m;try{let e=i(s),t={auth:p,callback:u.callback,context:f,outputSchema:u.outputSchema,turnPolicy:u.turnPolicy};m=u.inputResponses===void 0?await e.send(u.message,t):await e.respond(u.inputResponses,t)}catch(e){let n=logError(log,`session-message request failed`,e,{sessionId:s});return Response.json({error:`Failed to send the session message.`,errorId:n,ok:!1},{status:500})}return m.status===`session_not_active`?Response.json({code:`session_not_active`,error:`The session is no longer active.`,ok:!1},{headers:{"cache-control":`no-store`},status:409}):Response.json({ok:!0,sessionId:m.sessionId,status:`accepted`},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:m.sessionId},status:202})}),POST(EVE_SESSION_CANCEL_ROUTE_PATTERN,async(n,{attachSession:r,params:i})=>{let a=await routeAuth(n,e.auth);if(a instanceof Response)return a;let o=requireSessionId(i);if(o instanceof Response)return o;let s=await parseCancelTurnBody(n);if(s instanceof Response)return s;let c;try{c=await r(o).cancel({taskId:s.taskId,turnId:s.turnId})}catch(e){let n=logError(log,`cancel-turn request failed`,e,{sessionId:o});return Response.json({error:`Failed to cancel the turn.`,errorId:n,ok:!1},{status:500})}return Response.json(c.status===`accepted`?{ok:!0,sessionId:c.sessionId,status:`accepted`}:{ok:!0,status:`no_active_turn`},{headers:{"cache-control":`no-store`},status:c.status===`accepted`?202:200})}),POST(EVE_SESSION_COMPACT_ROUTE_PATTERN,async(n,{attachSession:r,params:i})=>{let a=await routeAuth(n,e.auth);if(a instanceof Response)return a;let o=requireSessionId(i);if(o instanceof Response)return o;let s=await parseSessionControlBody(n);if(s instanceof Response)return s;let c;try{c=await r(o).compact()}catch(e){let n=logError(log,`session-compaction request failed`,e,{sessionId:o});return Response.json({error:`Failed to compact the session.`,errorId:n,ok:!1},{status:500})}return Response.json(c.status===`accepted`?{ok:!0,sessionId:c.sessionId,status:`accepted`}:{ok:!0,status:`no_active_session`},{headers:{"cache-control":`no-store`},status:c.status===`accepted`?202:200})}),POST(EVE_SESSION_CLEAR_ROUTE_PATTERN,async(n,{attachSession:r,params:i})=>{let a=await routeAuth(n,e.auth);if(a instanceof Response)return a;let o=requireSessionId(i);if(o instanceof Response)return o;let s=await parseSessionControlBody(n);if(s instanceof Response)return s;let c;try{c=await r(o).clear()}catch(e){let n=logError(log,`session-clear request failed`,e,{sessionId:o});return Response.json({error:`Failed to clear the session context.`,errorId:n,ok:!1},{status:500})}return Response.json(c.status===`accepted`?{ok:!0,sessionId:c.sessionId,status:`accepted`}:{ok:!0,status:`no_active_session`},{headers:{"cache-control":`no-store`},status:c.status===`accepted`?202:200})}),POST(EVE_SESSION_RESET_ROUTE_PATTERN,async(n,{attachSession:r,params:i})=>{let a=await routeAuth(n,e.auth);if(a instanceof Response)return a;let o=requireSessionId(i);if(o instanceof Response)return o;let s=await parseResetBody(n);if(s instanceof Response)return s;let c;try{c=await r(o).reset({reason:s.reason})}catch(e){let n=logError(log,`session-reset request failed`,e,{sessionId:o});return Response.json({error:`Failed to reset the session.`,errorId:n,ok:!1},{status:500})}return Response.json(c.status===`reset`?{ok:!0,previousSessionId:c.previousSessionId,status:`reset`}:{ok:!0,status:`no_active_session`},{headers:{"cache-control":`no-store`}})}),GET(EVE_SESSION_STREAM_ROUTE_PATTERN,async(t,{attachSession:n,params:r})=>{let i=await routeAuth(t,e.auth);if(i instanceof Response)return i;let a=requireSessionId(r);return a instanceof Response?a:await createSessionStreamResponse(t,n(a))}),GET(EVE_SUBAGENT_STREAM_ROUTE_PATTERN,async(t,n)=>{let r=await routeAuth(t,e.auth);if(r instanceof Response)return r;let i=n.params.parentSessionId,a=n.params.callId,o=n.params.childSessionId;if(!i||!a||!o)return Response.json({error:`Missing subagent stream coordinates.`,ok:!1},{status:400});let s=parseStartIndex(t);if(s instanceof Response)return s;let c=parseIncludeTailIndex(t),l=createEveSubagentStreamRoutePath({callId:a,childSessionId:o,parentSessionId:i}),u;try{let e=await findRemoteSubagentBinding({callId:a,childSessionId:o,childStreamPath:l,parentSessionId:i,parent:n.attachSession(i)});if(e===void 0)throw Error(`Remote subagent binding not found.`);u=e}catch{return Response.json({error:`Subagent stream not found.`,ok:!1},{status:404})}let d=readRemoteAgentStreamHeadersResolver(n);if(d===void 0)return Response.json({error:`Subagent stream proxy requires internal channel dispatch context.`,ok:!1},{status:500});let f;try{f=await d({name:u.data.toolName,resolverId:u.data.remote.resolverId,url:u.data.remote.url})}catch{return Response.json({error:`Subagent stream not found.`,ok:!1},{status:404})}let p=new URL(createEveSessionStreamRoutePath(o).replace(/^\/+/,``),`${u.data.remote.url.replace(/\/+$/,``)}/`);s!==void 0&&p.searchParams.set(`startIndex`,String(s)),c&&p.searchParams.set(`includeTailIndex`,`1`);let g=await fetch(p,{cache:`no-store`,headers:f,redirect:`manual`,signal:t.signal}),_=new Headers;for(let e of[`cache-control`,`content-type`,`x-accel-buffering`,EVE_SESSION_ID_HEADER,EVE_STREAM_FORMAT_HEADER,EVE_STREAM_TAIL_INDEX_HEADER,EVE_STREAM_VERSION_HEADER]){let t=g.headers.get(e);t!==null&&_.set(e,t)}return new Response(g.body,{headers:_,status:g.status,statusText:g.statusText})})],events:e.events})}async function findRemoteSubagentBinding(e){let t=await e.parent.getStreamTailIndex();if(t<0)return;let n=(await e.parent.getEventStream({startIndex:0})).getReader(),r;try{for(let i=0;i<=t;i+=1){let t=await n.read();if(t.done)break;let i=t.value;i.type===`subagent.called`&&i.data.sessionId===e.parentSessionId&&i.data.callId===e.callId&&i.data.childSessionId===e.childSessionId&&i.data.childStreamPath===e.childStreamPath&&i.data.remote!==void 0&&(r=i)}}finally{await n.cancel().catch(()=>{})}return r}function normalizeEveCors(e){if(e===void 0||e===!1)return!1;if(e===!0)return!0;let t={};return e.origin!==void 0&&(t.origin=normalizeEveCorsOrigin(e.origin)),e.methods!==void 0&&(t.methods=e.methods),e.allowedHeaders!==void 0&&(t.allowHeaders=e.allowedHeaders),e.exposedHeaders!==void 0&&(t.exposeHeaders=e.exposedHeaders),e.credentials!==void 0&&(t.credentials=e.credentials),e.maxAge!==void 0&&(t.maxAge=e.maxAge),e.preflightStatus!==void 0&&(t.preflight={statusCode:e.preflightStatus}),t}function normalizeEveCorsOrigin(e){return e===`*`||e===`null`?e:typeof e==`string`?[e]:e}async function resolveOnMessage(e){let n=e.config.onMessage??defaultOnMessage,r;try{if(r=await n({eve:e.sessionId===void 0?{caller:e.auth,request:e.request}:{caller:e.auth,request:e.request,sessionId:e.sessionId}},e.message),r==null)throw TypeError(`eveChannel onMessage must return an auth result.`)}catch(n){let r=logError(log,`onMessage handler failed`,n,{sessionId:e.sessionId});return Response.json({error:`onMessage handler failed.`,errorId:r,ok:!1},{status:500})}return{auth:r.auth,context:r.context,title:r.title}}function defaultOnMessage(e){return{auth:defaultEveAuth(e)}}async function deriveOperationContinuationToken(e){let t=JSON.stringify([`eve:create-session:v1`,e.auth.authenticator,e.auth.issuer??null,e.auth.principalType,e.auth.principalId,e.operationId]),n=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(t));return`eve:op:${Array.from(new Uint8Array(n),e=>e.toString(16).padStart(2,`0`)).join(``).slice(0,32)}`}function parseCreateBody(e){if(e.inputResponses!==void 0)return Response.json({error:`'inputResponses' is only accepted for an existing session.`,ok:!1},{status:400});let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseCapabilitiesField(e.capabilities);if(i instanceof Response)return i;let a=parseModeField(e.mode);if(a instanceof Response)return a;let o=parseOutputSchemaField(e.outputSchema);if(o instanceof Response)return o;if(t===void 0)return Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400});let s=e.operationId;if(s!==void 0&&(typeof s!=`string`||!s))return Response.json({error:`Expected 'operationId' to be a non-empty string.`,ok:!1},{status:400});let c={callback:r,capabilities:i,message:t,mode:a,context:n,outputSchema:o};return typeof s==`string`&&(c.operationId=s),c}function parseSessionMessageBody(e){let t=rejectSessionContinuationToken(e);if(t!==null)return t;let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseInputResponses$1(e.inputResponses);if(i instanceof Response)return i;let a=parseClientContextField(e.clientContext);if(a instanceof Response)return a;let o=parseOutputSchemaField(e.outputSchema);if(o instanceof Response)return o;let s=parseTurnPolicyField(e.turnPolicy);return s instanceof Response?s:n===void 0&&i===void 0?Response.json({error:`Expected a non-empty 'message' or a non-empty 'inputResponses' array.`,ok:!1},{status:400}):n!==void 0&&i!==void 0?Response.json({error:`'message' and 'inputResponses' are mutually exclusive.`,ok:!1},{status:400}):{callback:r,message:n,inputResponses:i,context:a,outputSchema:o,turnPolicy:s}}async function parseCancelTurnBody(e){let t=await parseOptionalJsonRequest(e);if(t instanceof Response)return t;let n=rejectSessionContinuationToken(t);if(n!==null)return n;let r=t.turnId,i=t.taskId;if(r!==void 0&&(typeof r!=`string`||r.length===0))return Response.json({error:`Expected 'turnId' to be a non-empty string.`,ok:!1},{status:400});if(i!==void 0&&(typeof i!=`string`||i.length===0))return Response.json({error:`Expected 'taskId' to be a non-empty string.`,ok:!1},{status:400});let a={};return typeof i==`string`&&(a.taskId=i),typeof r==`string`&&(a.turnId=r),a}async function parseJsonRequest(e){let t;try{t=await e.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}return typeof t!=`object`||!t||Array.isArray(t)?Response.json({error:`Expected a JSON object.`,ok:!1},{status:400}):t}async function parseResetBody(e){let t=await parseOptionalJsonRequest(e);if(t instanceof Response)return t;let n=rejectSessionContinuationToken(t);if(n!==null)return n;let r=t.reason;return r!==void 0&&(typeof r!=`string`||r.length===0)?Response.json({error:`Expected 'reason' to be a non-empty string.`,ok:!1},{status:400}):r===void 0?{}:{reason:r}}async function parseSessionControlBody(e){let t=await parseOptionalJsonRequest(e);return t instanceof Response?t:rejectSessionContinuationToken(t)??t}async function parseOptionalJsonRequest(e){let t;try{t=await e.text()}catch{return Response.json({error:`Unreadable request body.`,ok:!1},{status:400})}if(t.trim().length===0)return{};let n;try{n=JSON.parse(t)}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}return typeof n!=`object`||!n||Array.isArray(n)?Response.json({error:`Expected a JSON object.`,ok:!1},{status:400}):n}function rejectSessionContinuationToken(e){return`continuationToken`in e?Response.json({error:`Session-ID routes do not accept 'continuationToken'.`,ok:!1},{status:400}):null}function requireSessionId(e){return e.sessionId||Response.json({error:`Missing session id.`,ok:!1},{status:400})}async function createSessionStreamResponse(e,t){let n=parseStartIndex(e);if(n instanceof Response)return n;let r=parseIncludeTailIndex(e);try{let i=r?await t.getStreamTailIndex():void 0,a=await t.getEventStream({startIndex:n}),o=new Headers({"cache-control":`no-store, no-transform`,"content-type":EVE_MESSAGE_STREAM_CONTENT_TYPE,"x-accel-buffering":`no`,[EVE_SESSION_ID_HEADER]:t.id,[EVE_STREAM_FORMAT_HEADER]:EVE_MESSAGE_STREAM_FORMAT,[EVE_STREAM_VERSION_HEADER]:EVE_MESSAGE_STREAM_VERSION});return i!==void 0&&o.set(EVE_STREAM_TAIL_INDEX_HEADER,String(i)),new Response(serializeAsNdjson(a,e.signal),{headers:o})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseCapabilitiesField(e){if(e===void 0)return;if(typeof e!=`object`||!e||Array.isArray(e))return Response.json({error:`Expected 'capabilities' to be an object.`,ok:!1},{status:400});let t=Object.keys(e),n=Reflect.get(e,`requestInput`);return t.some(e=>e!==`requestInput`)||n!==void 0&&typeof n!=`boolean`?Response.json({error:`Expected 'capabilities.requestInput' to be a boolean when provided.`,ok:!1},{status:400}):n===void 0?{}:{requestInput:n}}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseTurnPolicyField(e){if(e!==void 0)return e===`queue`||e===`steer`?e:Response.json({error:`Expected 'turnPolicy' to be either 'queue' or 'steer'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});if(hasInternalRefScheme(t.data))return Response.json({error:`File part 'data' must not use a framework-internal ref scheme.`,ok:!1},{status:400});let e={type:`file`,mediaType:t.mediaType,data:t.data};return typeof t.filename==`string`&&t.filename.length>0&&(e.filename=t.filename),e}return Response.json({error:`Unsupported message part type "${String(t.type)}". Use 'text' or 'file'.`,ok:!1},{status:400})}function checkUploadPolicy(e,t){if(!e.message)return null;let n=collectUploadPolicyViolations(e.message,t);if(n.length===0)return null;let[r]=n;if(!r)return null;let i=r.kind===`too-large`?413:415;return Response.json({error:formatUploadPolicyViolation(r),ok:!1,violations:n.map(e=>e.kind===`too-large`?{byteLength:e.byteLength,filename:e.filename,kind:e.kind,limit:e.limit,mediaType:e.mediaType}:{allowedMediaTypes:e.allowedMediaTypes,filename:e.filename,kind:e.kind,mediaType:e.mediaType})},{status:i})}function parseInputResponses$1(e){if(e===void 0)return;if(!Array.isArray(e)||e.length===0)return Response.json({error:`Expected 'inputResponses' to be a non-empty array.`,ok:!1},{status:400});let t=e.filter(isInputResponse);return t.length===e.length?t:Response.json({error:`Expected every 'inputResponses' entry to match the HITL response schema.`,ok:!1},{status:400})}function mergeContext(e,t){return e===void 0?t:t===void 0?e:[...e,...t]}function parseClientContextField(e){if(e!==void 0){if(typeof e==`string`)return e.length>0?[toClientContextMessage(e)]:void 0;if(Array.isArray(e))return e.length===0?void 0:e.every(e=>typeof e==`string`&&e.length>0)?e.map(e=>toClientContextMessage(e)):Response.json({error:`Expected 'clientContext' array entries to be non-empty strings.`,ok:!1},{status:400});if(typeof e!=`object`||!e)return Response.json({error:`Expected 'clientContext' to be a string, string array, or JSON object.`,ok:!1},{status:400});try{let t=parseJsonObject(e);return[toClientContextMessage(JSON.stringify(t))]}catch{return Response.json({error:`Expected 'clientContext' to be a JSON-serializable object.`,ok:!1},{status:400})}}}function toClientContextMessage(e){return`Client context:
|
|
2
|
-
${e}`}function parseIncludeTailIndex(e){let t=new URL(e.url).searchParams.get(`includeTailIndex`);return t===`1`||t===`true`}function parseStartIndex(e){let t=new URL(e.url).searchParams.get(`startIndex`);if(t===null)return;let n=Number(t);return!/^-?\d+$/.test(t)||!Number.isSafeInteger(n)?Response.json({error:`Expected startIndex to be an integer.`,ok:!1},{status:400}):n}function
|
|
3
|
-
`))},transform(e,t){t.enqueue(
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{isRuntimeSessionOwnershipConflictError}from"#execution/runtime-errors.js";import{isInputResponse}from"#runtime/input/types.js";import{hasInternalRefScheme}from"#internal/attachments/url-refs.js";import{EVE_INFO_ROUTE_PATH,EVE_SESSION_CANCEL_ROUTE_PATTERN,EVE_SESSION_CLEAR_ROUTE_PATTERN,EVE_SESSION_COMPACT_ROUTE_PATTERN,EVE_SESSION_RESET_ROUTE_PATTERN,EVE_SESSION_ROUTE_PATH,EVE_SESSION_ROUTE_PATTERN,EVE_SESSION_STREAM_ROUTE_PATTERN,EVE_SUBAGENT_STREAM_ROUTE_PATTERN,createEveSessionStreamRoutePath,createEveSubagentStreamRoutePath}from"#protocol/routes.js";import{EVE_MESSAGE_STREAM_CONTENT_TYPE,EVE_MESSAGE_STREAM_FORMAT,EVE_MESSAGE_STREAM_VERSION,EVE_SESSION_ID_HEADER,EVE_STREAM_FORMAT_HEADER,EVE_STREAM_TAIL_INDEX_HEADER,EVE_STREAM_VERSION_HEADER}from"#protocol/message.js";import{parseJsonObject}from"#shared/json.js";import{GET,POST,defineChannel}from"#public/definitions/channel.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{parseTraceparent}from"#protocol/traceparent.js";import{routeAuth}from"#public/channels/auth.js";import{readAgentInfoRouteResponse,readRemoteAgentStreamHeadersResolver,readRouteSessionCreator}from"#internal/nitro/routes/channel-route-context.js";import{resolveForwardedPrincipal}from"#channel/forwarded-principal.js";import{collectUploadPolicyViolations,formatUploadPolicyViolation,mergeUploadPolicy}from"#public/channels/upload-policy.js";const log=createLogger(`eve.channel`);function defaultEveAuth(e){return e.eve.caller}function eveChannel(e){let r=mergeUploadPolicy(e.uploadPolicy);return defineChannel({cors:normalizeEveCors(e.cors),turnPolicy:e.turnPolicy,routes:[GET(EVE_INFO_ROUTE_PATH,async(t,n)=>{let r=await routeAuth(t,e.auth);if(r instanceof Response)return r;let i=readAgentInfoRouteResponse(n);return i===void 0?Response.json({error:`Agent info route requires internal channel dispatch context.`,ok:!1},{status:500}):await i()}),POST(EVE_SESSION_ROUTE_PATH,async(i,a)=>{let o=await routeAuth(i,e.auth);if(o instanceof Response)return o;let s=await parseJsonRequest(i);if(s instanceof Response)return s;let c=rejectSessionContinuationToken(s);if(c!==null)return c;let l=await resolveForwardedPrincipal({trustedForwarders:e.trustedForwarders,forwarder:o,payload:s});if(l instanceof Response)return l;let u=parseCreateBody(s);if(u instanceof Response)return u;let d=u.callback===void 0?void 0:parseTraceparent(i.headers.get(`traceparent`)),f=checkUploadPolicy(u,r);if(f!==null)return f;if(u.operationId!==void 0&&l.auth.principalType===`anonymous`)return Response.json({error:`operationId requires an authenticated principal.`,ok:!1},{status:400});let p=u.operationId===void 0?void 0:await deriveOperationContinuationToken({auth:l.auth,operationId:u.operationId});if(p!==void 0){let e=await a.resolveSession(p);if(e!==void 0)return Response.json({ok:!0,sessionId:e.id,status:`accepted`},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:e.id},status:202})}let m=await resolveOnMessage({auth:l.auth,config:e,message:u.message,request:i});if(m instanceof Response)return m;let h=readRouteSessionCreator(a);if(h===void 0)return Response.json({error:`Session creation requires internal channel dispatch context.`,ok:!1},{status:500});let g;try{g=await h({auth:m.auth,capabilities:u.capabilities??(u.mode===`task`?void 0:{requestInput:!0}),callback:u.callback,continuationToken:p,initiatorAuth:l.accepted?l.initiatorAuth:void 0,input:{message:u.message,context:mergeContext(u.context,m.context),outputSchema:u.outputSchema},mode:u.mode??`conversation`,parentTraceContext:d,title:m.title})}catch(e){if(p!==void 0&&isRuntimeSessionOwnershipConflictError(e))return Response.json({ok:!0,sessionId:e.ownerSessionId,status:`accepted`},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:e.ownerSessionId},status:202});let r=logError(log,`session-create request failed`,e);return Response.json({error:`Failed to create the session.`,errorId:r,ok:!1},{status:500})}return Response.json({ok:!0,sessionId:g.sessionId,status:`accepted`},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:g.sessionId},status:202})}),POST(EVE_SESSION_ROUTE_PATTERN,async(n,{attachSession:i,params:a})=>{let o=await routeAuth(n,e.auth);if(o instanceof Response)return o;let s=requireSessionId(a);if(s instanceof Response)return s;let c=await parseJsonRequest(n);if(c instanceof Response)return c;let l=await resolveForwardedPrincipal({trustedForwarders:e.trustedForwarders,forwarder:o,payload:c});if(l instanceof Response)return l;let u=parseSessionMessageBody(c);if(u instanceof Response)return u;let d=checkUploadPolicy(u,r);if(d!==null)return d;let f=u.context,p=l.auth;if(u.message!==void 0){let t=await resolveOnMessage({auth:l.auth,config:e,message:u.message,request:n,sessionId:s});if(t instanceof Response)return t;f=mergeContext(u.context,t.context),p=t.auth}let m;try{let e=i(s),t={auth:p,callback:u.callback,context:f,outputSchema:u.outputSchema,turnPolicy:u.turnPolicy};m=u.inputResponses===void 0?await e.send(u.message,t):await e.respond(u.inputResponses,t)}catch(e){let n=logError(log,`session-message request failed`,e,{sessionId:s});return Response.json({error:`Failed to send the session message.`,errorId:n,ok:!1},{status:500})}return m.status===`session_not_active`?Response.json({code:`session_not_active`,error:`The session is no longer active.`,ok:!1},{headers:{"cache-control":`no-store`},status:409}):Response.json({ok:!0,sessionId:m.sessionId,status:`accepted`},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:m.sessionId},status:202})}),POST(EVE_SESSION_CANCEL_ROUTE_PATTERN,async(n,{attachSession:r,params:i})=>{let a=await routeAuth(n,e.auth);if(a instanceof Response)return a;let o=requireSessionId(i);if(o instanceof Response)return o;let s=await parseCancelTurnBody(n);if(s instanceof Response)return s;let c;try{c=await r(o).cancel({taskId:s.taskId,turnId:s.turnId})}catch(e){let n=logError(log,`cancel-turn request failed`,e,{sessionId:o});return Response.json({error:`Failed to cancel the turn.`,errorId:n,ok:!1},{status:500})}return Response.json(c.status===`accepted`?{ok:!0,sessionId:c.sessionId,status:`accepted`}:{ok:!0,status:`no_active_turn`},{headers:{"cache-control":`no-store`},status:c.status===`accepted`?202:200})}),POST(EVE_SESSION_COMPACT_ROUTE_PATTERN,async(n,{attachSession:r,params:i})=>{let a=await routeAuth(n,e.auth);if(a instanceof Response)return a;let o=requireSessionId(i);if(o instanceof Response)return o;let s=await parseSessionControlBody(n);if(s instanceof Response)return s;let c;try{c=await r(o).compact()}catch(e){let n=logError(log,`session-compaction request failed`,e,{sessionId:o});return Response.json({error:`Failed to compact the session.`,errorId:n,ok:!1},{status:500})}return Response.json(c.status===`accepted`?{ok:!0,sessionId:c.sessionId,status:`accepted`}:{ok:!0,status:`no_active_session`},{headers:{"cache-control":`no-store`},status:c.status===`accepted`?202:200})}),POST(EVE_SESSION_CLEAR_ROUTE_PATTERN,async(n,{attachSession:r,params:i})=>{let a=await routeAuth(n,e.auth);if(a instanceof Response)return a;let o=requireSessionId(i);if(o instanceof Response)return o;let s=await parseSessionControlBody(n);if(s instanceof Response)return s;let c;try{c=await r(o).clear()}catch(e){let n=logError(log,`session-clear request failed`,e,{sessionId:o});return Response.json({error:`Failed to clear the session context.`,errorId:n,ok:!1},{status:500})}return Response.json(c.status===`accepted`?{ok:!0,sessionId:c.sessionId,status:`accepted`}:{ok:!0,status:`no_active_session`},{headers:{"cache-control":`no-store`},status:c.status===`accepted`?202:200})}),POST(EVE_SESSION_RESET_ROUTE_PATTERN,async(n,{attachSession:r,params:i})=>{let a=await routeAuth(n,e.auth);if(a instanceof Response)return a;let o=requireSessionId(i);if(o instanceof Response)return o;let s=await parseResetBody(n);if(s instanceof Response)return s;let c;try{c=await r(o).reset({reason:s.reason})}catch(e){let n=logError(log,`session-reset request failed`,e,{sessionId:o});return Response.json({error:`Failed to reset the session.`,errorId:n,ok:!1},{status:500})}return Response.json(c.status===`reset`?{ok:!0,previousSessionId:c.previousSessionId,status:`reset`}:{ok:!0,status:`no_active_session`},{headers:{"cache-control":`no-store`}})}),GET(EVE_SESSION_STREAM_ROUTE_PATTERN,async(t,{attachSession:n,params:r})=>{let i=await routeAuth(t,e.auth);if(i instanceof Response)return i;let a=requireSessionId(r);return a instanceof Response?a:await createSessionStreamResponse(t,n(a))}),GET(EVE_SUBAGENT_STREAM_ROUTE_PATTERN,async(t,n)=>{let r=await routeAuth(t,e.auth);if(r instanceof Response)return r;let i=n.params.parentSessionId,a=n.params.callId,o=n.params.childSessionId;if(!i||!a||!o)return Response.json({error:`Missing subagent stream coordinates.`,ok:!1},{status:400});let s=parseStartIndex(t);if(s instanceof Response)return s;let c=parseIncludeTailIndex(t),l=createEveSubagentStreamRoutePath({callId:a,childSessionId:o,parentSessionId:i}),u;try{let e=await findRemoteSubagentBinding({callId:a,childSessionId:o,childStreamPath:l,parentSessionId:i,parent:n.attachSession(i)});if(e===void 0)throw Error(`Remote subagent binding not found.`);u=e}catch{return Response.json({error:`Subagent stream not found.`,ok:!1},{status:404})}let d=readRemoteAgentStreamHeadersResolver(n);if(d===void 0)return Response.json({error:`Subagent stream proxy requires internal channel dispatch context.`,ok:!1},{status:500});let f;try{f=await d({name:u.data.toolName,resolverId:u.data.remote.resolverId,url:u.data.remote.url})}catch{return Response.json({error:`Subagent stream not found.`,ok:!1},{status:404})}let p=new URL(createEveSessionStreamRoutePath(o).replace(/^\/+/,``),`${u.data.remote.url.replace(/\/+$/,``)}/`);s!==void 0&&p.searchParams.set(`startIndex`,String(s)),c&&p.searchParams.set(`includeTailIndex`,`1`);let g=await fetch(p,{cache:`no-store`,headers:f,redirect:`manual`,signal:t.signal}),_=new Headers;for(let e of[`cache-control`,`content-type`,`x-accel-buffering`,EVE_SESSION_ID_HEADER,EVE_STREAM_FORMAT_HEADER,EVE_STREAM_TAIL_INDEX_HEADER,EVE_STREAM_VERSION_HEADER]){let t=g.headers.get(e);t!==null&&_.set(e,t)}return new Response(g.body,{headers:_,status:g.status,statusText:g.statusText})})],events:e.events})}async function findRemoteSubagentBinding(e){let t=await e.parent.getStreamTailIndex();if(t<0)return;let n=(await e.parent.getEventStream({startIndex:0})).getReader(),r;try{for(let i=0;i<=t;i+=1){let t=await n.read();if(t.done)break;let i=t.value;i.type===`subagent.called`&&i.data.sessionId===e.parentSessionId&&i.data.callId===e.callId&&i.data.childSessionId===e.childSessionId&&i.data.childStreamPath===e.childStreamPath&&i.data.remote!==void 0&&(r=i)}}finally{await n.cancel().catch(()=>{})}return r}function normalizeEveCors(e){if(e===void 0||e===!1)return!1;if(e===!0)return!0;let t={};return e.origin!==void 0&&(t.origin=normalizeEveCorsOrigin(e.origin)),e.methods!==void 0&&(t.methods=e.methods),e.allowedHeaders!==void 0&&(t.allowHeaders=e.allowedHeaders),e.exposedHeaders!==void 0&&(t.exposeHeaders=e.exposedHeaders),e.credentials!==void 0&&(t.credentials=e.credentials),e.maxAge!==void 0&&(t.maxAge=e.maxAge),e.preflightStatus!==void 0&&(t.preflight={statusCode:e.preflightStatus}),t}function normalizeEveCorsOrigin(e){return e===`*`||e===`null`?e:typeof e==`string`?[e]:e}async function resolveOnMessage(e){let n=e.config.onMessage??defaultOnMessage,r;try{if(r=await n({eve:e.sessionId===void 0?{caller:e.auth,request:e.request}:{caller:e.auth,request:e.request,sessionId:e.sessionId}},e.message),r==null)throw TypeError(`eveChannel onMessage must return an auth result.`)}catch(n){let r=logError(log,`onMessage handler failed`,n,{sessionId:e.sessionId});return Response.json({error:`onMessage handler failed.`,errorId:r,ok:!1},{status:500})}return{auth:r.auth,context:r.context,title:r.title}}function defaultOnMessage(e){return{auth:defaultEveAuth(e)}}async function deriveOperationContinuationToken(e){let t=JSON.stringify([`eve:create-session:v1`,e.auth.authenticator,e.auth.issuer??null,e.auth.principalType,e.auth.principalId,e.operationId]),n=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(t));return`eve:op:${Array.from(new Uint8Array(n),e=>e.toString(16).padStart(2,`0`)).join(``).slice(0,32)}`}function parseCreateBody(e){if(e.inputResponses!==void 0)return Response.json({error:`'inputResponses' is only accepted for an existing session.`,ok:!1},{status:400});let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseCapabilitiesField(e.capabilities);if(i instanceof Response)return i;let a=parseModeField(e.mode);if(a instanceof Response)return a;let o=parseOutputSchemaField(e.outputSchema);if(o instanceof Response)return o;if(t===void 0)return Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400});let s=e.operationId;if(s!==void 0&&(typeof s!=`string`||!s))return Response.json({error:`Expected 'operationId' to be a non-empty string.`,ok:!1},{status:400});let c={callback:r,capabilities:i,message:t,mode:a,context:n,outputSchema:o};return typeof s==`string`&&(c.operationId=s),c}function parseSessionMessageBody(e){let t=rejectSessionContinuationToken(e);if(t!==null)return t;let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseInputResponses$1(e.inputResponses);if(i instanceof Response)return i;let a=parseClientContextField(e.clientContext);if(a instanceof Response)return a;let o=parseOutputSchemaField(e.outputSchema);if(o instanceof Response)return o;let s=parseTurnPolicyField(e.turnPolicy);return s instanceof Response?s:n===void 0&&i===void 0?Response.json({error:`Expected a non-empty 'message' or a non-empty 'inputResponses' array.`,ok:!1},{status:400}):n!==void 0&&i!==void 0?Response.json({error:`'message' and 'inputResponses' are mutually exclusive.`,ok:!1},{status:400}):{callback:r,message:n,inputResponses:i,context:a,outputSchema:o,turnPolicy:s}}async function parseCancelTurnBody(e){let t=await parseOptionalJsonRequest(e);if(t instanceof Response)return t;let n=rejectSessionContinuationToken(t);if(n!==null)return n;let r=t.turnId,i=t.taskId;if(r!==void 0&&(typeof r!=`string`||r.length===0))return Response.json({error:`Expected 'turnId' to be a non-empty string.`,ok:!1},{status:400});if(i!==void 0&&(typeof i!=`string`||i.length===0))return Response.json({error:`Expected 'taskId' to be a non-empty string.`,ok:!1},{status:400});let a={};return typeof i==`string`&&(a.taskId=i),typeof r==`string`&&(a.turnId=r),a}async function parseJsonRequest(e){let t;try{t=await e.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}return typeof t!=`object`||!t||Array.isArray(t)?Response.json({error:`Expected a JSON object.`,ok:!1},{status:400}):t}async function parseResetBody(e){let t=await parseOptionalJsonRequest(e);if(t instanceof Response)return t;let n=rejectSessionContinuationToken(t);if(n!==null)return n;let r=t.reason;return r!==void 0&&(typeof r!=`string`||r.length===0)?Response.json({error:`Expected 'reason' to be a non-empty string.`,ok:!1},{status:400}):r===void 0?{}:{reason:r}}async function parseSessionControlBody(e){let t=await parseOptionalJsonRequest(e);return t instanceof Response?t:rejectSessionContinuationToken(t)??t}async function parseOptionalJsonRequest(e){let t;try{t=await e.text()}catch{return Response.json({error:`Unreadable request body.`,ok:!1},{status:400})}if(t.trim().length===0)return{};let n;try{n=JSON.parse(t)}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}return typeof n!=`object`||!n||Array.isArray(n)?Response.json({error:`Expected a JSON object.`,ok:!1},{status:400}):n}function rejectSessionContinuationToken(e){return`continuationToken`in e?Response.json({error:`Session-ID routes do not accept 'continuationToken'.`,ok:!1},{status:400}):null}function requireSessionId(e){return e.sessionId||Response.json({error:`Missing session id.`,ok:!1},{status:400})}async function createSessionStreamResponse(e,t){let n=parseStartIndex(e);if(n instanceof Response)return n;let r=parseIncludeTailIndex(e);try{let i=r?await t.getStreamTailIndex():void 0,a=await t.getEventStream({startIndex:n}),o=new Headers({"cache-control":`no-store, no-transform`,"content-type":EVE_MESSAGE_STREAM_CONTENT_TYPE,"x-accel-buffering":`no`,[EVE_SESSION_ID_HEADER]:t.id,[EVE_STREAM_FORMAT_HEADER]:EVE_MESSAGE_STREAM_FORMAT,[EVE_STREAM_VERSION_HEADER]:EVE_MESSAGE_STREAM_VERSION});return i!==void 0&&o.set(EVE_STREAM_TAIL_INDEX_HEADER,String(i)),new Response(serializeAsNdjson(a,e.signal,streamEventLimit(n,i)),{headers:o})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseCapabilitiesField(e){if(e===void 0)return;if(typeof e!=`object`||!e||Array.isArray(e))return Response.json({error:`Expected 'capabilities' to be an object.`,ok:!1},{status:400});let t=Object.keys(e),n=Reflect.get(e,`requestInput`);return t.some(e=>e!==`requestInput`)||n!==void 0&&typeof n!=`boolean`?Response.json({error:`Expected 'capabilities.requestInput' to be a boolean when provided.`,ok:!1},{status:400}):n===void 0?{}:{requestInput:n}}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseTurnPolicyField(e){if(e!==void 0)return e===`queue`||e===`steer`?e:Response.json({error:`Expected 'turnPolicy' to be either 'queue' or 'steer'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});if(hasInternalRefScheme(t.data))return Response.json({error:`File part 'data' must not use a framework-internal ref scheme.`,ok:!1},{status:400});let e={type:`file`,mediaType:t.mediaType,data:t.data};return typeof t.filename==`string`&&t.filename.length>0&&(e.filename=t.filename),e}return Response.json({error:`Unsupported message part type "${String(t.type)}". Use 'text' or 'file'.`,ok:!1},{status:400})}function checkUploadPolicy(e,t){if(!e.message)return null;let n=collectUploadPolicyViolations(e.message,t);if(n.length===0)return null;let[r]=n;if(!r)return null;let i=r.kind===`too-large`?413:415;return Response.json({error:formatUploadPolicyViolation(r),ok:!1,violations:n.map(e=>e.kind===`too-large`?{byteLength:e.byteLength,filename:e.filename,kind:e.kind,limit:e.limit,mediaType:e.mediaType}:{allowedMediaTypes:e.allowedMediaTypes,filename:e.filename,kind:e.kind,mediaType:e.mediaType})},{status:i})}function parseInputResponses$1(e){if(e===void 0)return;if(!Array.isArray(e)||e.length===0)return Response.json({error:`Expected 'inputResponses' to be a non-empty array.`,ok:!1},{status:400});let t=e.filter(isInputResponse);return t.length===e.length?t:Response.json({error:`Expected every 'inputResponses' entry to match the HITL response schema.`,ok:!1},{status:400})}function mergeContext(e,t){return e===void 0?t:t===void 0?e:[...e,...t]}function parseClientContextField(e){if(e!==void 0){if(typeof e==`string`)return e.length>0?[toClientContextMessage(e)]:void 0;if(Array.isArray(e))return e.length===0?void 0:e.every(e=>typeof e==`string`&&e.length>0)?e.map(e=>toClientContextMessage(e)):Response.json({error:`Expected 'clientContext' array entries to be non-empty strings.`,ok:!1},{status:400});if(typeof e!=`object`||!e)return Response.json({error:`Expected 'clientContext' to be a string, string array, or JSON object.`,ok:!1},{status:400});try{let t=parseJsonObject(e);return[toClientContextMessage(JSON.stringify(t))]}catch{return Response.json({error:`Expected 'clientContext' to be a JSON-serializable object.`,ok:!1},{status:400})}}}function toClientContextMessage(e){return`Client context:
|
|
2
|
+
${e}`}function parseIncludeTailIndex(e){let t=new URL(e.url).searchParams.get(`includeTailIndex`);return t===`1`||t===`true`}function parseStartIndex(e){let t=new URL(e.url).searchParams.get(`startIndex`);if(t===null)return;let n=Number(t);return!/^-?\d+$/.test(t)||!Number.isSafeInteger(n)?Response.json({error:`Expected startIndex to be an integer.`,ok:!1},{status:400}):n}function streamEventLimit(e,t){if(t===void 0)return;let n=e===void 0?0:e<0?Math.max(0,t+1+e):e;return Math.max(0,t-n+1)}function serializeAsNdjson(e,t,n){let r=new TextEncoder,i=0,a=new TransformStream({start(e){e.enqueue(r.encode(`
|
|
3
|
+
`)),n===0&&e.terminate()},transform(e,t){t.enqueue(r.encode(`${JSON.stringify(e)}\n`)),i+=1,i===n&&t.terminate()}});return e.pipeTo(a.writable,{signal:t}).catch(()=>{}),a.readable}export{defaultEveAuth,eveChannel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { linqChannel, type LinqChannel, type LinqChannelConfig, type LinqChannelCredentials, type LinqCredentialValue, type LinqInboundMessageContext, type LinqInboundResult, type LinqInboundResultOrPromise, } from "#public/channels/linq/linqChannel.js";
|
|
1
|
+
export { defaultLinqAuth, linqChannel, type LinqChannel, type LinqChannelConfig, type LinqChannelCredentials, type LinqCredentialValue, type LinqInboundMessageContext, type LinqInboundResult, type LinqInboundResultOrPromise, } from "#public/channels/linq/linqChannel.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{linqChannel}from"#public/channels/linq/linqChannel.js";export{linqChannel};
|
|
1
|
+
import{defaultLinqAuth,linqChannel}from"#public/channels/linq/linqChannel.js";export{defaultLinqAuth,linqChannel};
|
|
@@ -38,7 +38,7 @@ export interface LinqChannelConfig {
|
|
|
38
38
|
readonly events?: ChatSdkChannelEvents<{
|
|
39
39
|
linq: ReturnType<typeof createLinqAdapter>;
|
|
40
40
|
}>;
|
|
41
|
-
/** Inbound message policy. Defaults to dispatching
|
|
41
|
+
/** Inbound message policy. Defaults to dispatching with the Linq message author's user auth. */
|
|
42
42
|
readonly onMessage?: (ctx: LinqInboundMessageContext, message: Message) => LinqInboundResultOrPromise;
|
|
43
43
|
/** Override the default webhook route (`/eve/v1/linq`). */
|
|
44
44
|
readonly route?: string;
|
|
@@ -64,3 +64,5 @@ export interface LinqChannel extends Channel<ChatSdkChannelState, ChatSdkReceive
|
|
|
64
64
|
* ```
|
|
65
65
|
*/
|
|
66
66
|
export declare function linqChannel(config: LinqChannelConfig): LinqChannel;
|
|
67
|
+
/** Default Linq auth projection for inbound Chat SDK message authors. */
|
|
68
|
+
export declare function defaultLinqAuth(message: Message): SessionAuthContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{vercelOidc}from"#public/channels/auth.js";import{chatSdkChannel}from"#public/channels/chat-sdk/index.js";import{createMemoryState}from"#compiled/@chat-adapter/state-memory/index.js";import{createLinqAdapter}from"#compiled/@linqapp/chat-sdk-adapter/index.js";import{linqInboundContent}from"#public/channels/linq/inboundContent.js";function linqChannel(e){let i=normalizeCredentials(e.credentials);e.baseURL!==void 0&&(i.baseURL=e.baseURL);let a=chatSdkChannel({adapters:{linq:createLinqAdapter(i)},concurrency:`concurrent`,events:e.events,routes:{linq:e.route??`/eve/v1/linq`},state:createMemoryState(),streaming:!1,turnPolicy:e.turnPolicy,userName:e.userName??`eve`}),o=e.onMessage??defaultOnMessage;return a.bot.onDirectMessage(async(e,t)=>{await dispatchMessage(a,o,e,t)}),a.bot.onNewMessage(/[\s\S]*/,async(e,t)=>{await dispatchMessage(a,o,e,t)}),a.channel}function normalizeCredentials(t){let{apiKey:n,signingSecret:r,webhookVerifier:i}=t??{},a={};return n!==void 0&&(a.credentials=createCredentialProvider(n,r)),i===void 0?r===void 0&&(a.webhookVerifier=vercelOidc()):a.webhookVerifier=i,a}function createCredentialProvider(e,t){return async()=>{let n={apiKey:await resolveCredentialValue(e)};return t!==void 0&&(n.signingSecret=await resolveCredentialValue(t)),n}}async function resolveCredentialValue(e){return typeof e==`function`?await e():e}async function defaultOnMessage(){return{auth:
|
|
1
|
+
import{vercelOidc}from"#public/channels/auth.js";import{chatSdkChannel}from"#public/channels/chat-sdk/index.js";import{createMemoryState}from"#compiled/@chat-adapter/state-memory/index.js";import{createLinqAdapter}from"#compiled/@linqapp/chat-sdk-adapter/index.js";import{linqInboundContent}from"#public/channels/linq/inboundContent.js";function linqChannel(e){let i=normalizeCredentials(e.credentials);e.baseURL!==void 0&&(i.baseURL=e.baseURL);let a=chatSdkChannel({adapters:{linq:createLinqAdapter(i)},concurrency:`concurrent`,events:e.events,routes:{linq:e.route??`/eve/v1/linq`},state:createMemoryState(),streaming:!1,turnPolicy:e.turnPolicy,userName:e.userName??`eve`}),o=e.onMessage??defaultOnMessage;return a.bot.onDirectMessage(async(e,t)=>{await dispatchMessage(a,o,e,t)}),a.bot.onNewMessage(/[\s\S]*/,async(e,t)=>{await dispatchMessage(a,o,e,t)}),a.channel}function normalizeCredentials(t){let{apiKey:n,signingSecret:r,webhookVerifier:i}=t??{},a={};return n!==void 0&&(a.credentials=createCredentialProvider(n,r)),i===void 0?r===void 0&&(a.webhookVerifier=vercelOidc()):a.webhookVerifier=i,a}function createCredentialProvider(e,t){return async()=>{let n={apiKey:await resolveCredentialValue(e)};return t!==void 0&&(n.signingSecret=await resolveCredentialValue(t)),n}}async function resolveCredentialValue(e){return typeof e==`function`?await e():e}function defaultLinqAuth(e){let t={};return e.author.userName!==void 0&&(t.user_name=e.author.userName),{attributes:t,authenticator:`linq-message`,issuer:`linq`,principalId:`linq:${e.author.userId}`,principalType:e.author.isBot?`service`:`user`,subject:e.author.userId}}async function defaultOnMessage(e,t){return{auth:defaultLinqAuth(t)}}async function dispatchMessage(e,t,n,r){let a=await t({thread:n},r);if(a===null)return;await markReadBestEffort(e.bot.getAdapter(`linq`),n,r);let o=linqInboundContent(r);o!==void 0&&await e.send({context:[...a.context??[]],message:o},{auth:a.auth,thread:n,title:a.title})}async function markReadBestEffort(e,t,n){try{await e.markRead(t.id,n.id)}catch{}}export{defaultLinqAuth,linqChannel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { photonIMessageChannel, type PhotonIMessageChannel, type PhotonIMessageChannelConfig, type PhotonIMessageChannelCredentials, type PhotonInboundMessageContext, type PhotonInboundResult, type PhotonInboundResultOrPromise, } from "#public/channels/photon/photonIMessageChannel.js";
|
|
1
|
+
export { defaultPhotonAuth, photonIMessageChannel, type PhotonIMessageChannel, type PhotonIMessageChannelConfig, type PhotonIMessageChannelCredentials, type PhotonInboundMessageContext, type PhotonInboundResult, type PhotonInboundResultOrPromise, } from "#public/channels/photon/photonIMessageChannel.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{photonIMessageChannel}from"#public/channels/photon/photonIMessageChannel.js";export{photonIMessageChannel};
|
|
1
|
+
import{defaultPhotonAuth,photonIMessageChannel}from"#public/channels/photon/photonIMessageChannel.js";export{defaultPhotonAuth,photonIMessageChannel};
|
|
@@ -26,7 +26,7 @@ export interface PhotonIMessageChannelConfig {
|
|
|
26
26
|
readonly events?: ChatSdkChannelEvents<{
|
|
27
27
|
imessage: iMessageAdapter;
|
|
28
28
|
}>;
|
|
29
|
-
/** Inbound message policy. Defaults to dispatching
|
|
29
|
+
/** Inbound message policy. Defaults to dispatching with the Photon message author's user auth. */
|
|
30
30
|
readonly onMessage?: (ctx: PhotonInboundMessageContext, message: Message) => PhotonInboundResultOrPromise;
|
|
31
31
|
/** Override the default webhook route (`/eve/v1/photon`). */
|
|
32
32
|
readonly route?: string;
|
|
@@ -56,3 +56,5 @@ export interface PhotonIMessageChannel extends ChatSdkChannel {
|
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
58
|
export declare function photonIMessageChannel(config: PhotonIMessageChannelConfig): PhotonIMessageChannel;
|
|
59
|
+
/** Default Photon auth projection for inbound Chat SDK message authors. */
|
|
60
|
+
export declare function defaultPhotonAuth(message: Message): SessionAuthContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{vercelOidc}from"#public/channels/auth.js";import{chatSdkChannel}from"#public/channels/chat-sdk/index.js";import{createMemoryState}from"#compiled/@chat-adapter/state-memory/index.js";import{createiMessageAdapter}from"#compiled/@photon-ai/chat-adapter-imessage/index.js";import{photonInboundContent}from"#public/channels/photon/inboundContent.js";function photonIMessageChannel(i){let a=i.webhookSecret??process.env.IMESSAGE_WEBHOOK_SECRET,o=chatSdkChannel({adapters:{imessage:createiMessageAdapter({credentials:i.credentials,...i.webhookVerifier?{webhookVerifier:i.webhookVerifier}:a?{webhookSecret:a}:{webhookVerifier:vercelOidc()}})},concurrency:`concurrent`,events:i.events,routes:{imessage:i.route??`/eve/v1/photon`},state:createMemoryState(),streaming:!1,turnPolicy:i.turnPolicy,userName:i.userName??`eve`}),s=i.onMessage??defaultOnMessage;return o.bot.onDirectMessage(async(e,t)=>{await dispatchMessage(o,s,e,t)}),o.bot.onNewMessage(/[\s\S]*/,async(e,t)=>{await dispatchMessage(o,s,e,t)}),o.channel}async function defaultOnMessage(){return{auth:
|
|
1
|
+
import{vercelOidc}from"#public/channels/auth.js";import{chatSdkChannel}from"#public/channels/chat-sdk/index.js";import{createMemoryState}from"#compiled/@chat-adapter/state-memory/index.js";import{createiMessageAdapter}from"#compiled/@photon-ai/chat-adapter-imessage/index.js";import{photonInboundContent}from"#public/channels/photon/inboundContent.js";function photonIMessageChannel(i){let a=i.webhookSecret??process.env.IMESSAGE_WEBHOOK_SECRET,o=chatSdkChannel({adapters:{imessage:createiMessageAdapter({credentials:i.credentials,...i.webhookVerifier?{webhookVerifier:i.webhookVerifier}:a?{webhookSecret:a}:{webhookVerifier:vercelOidc()}})},concurrency:`concurrent`,events:i.events,routes:{imessage:i.route??`/eve/v1/photon`},state:createMemoryState(),streaming:!1,turnPolicy:i.turnPolicy,userName:i.userName??`eve`}),s=i.onMessage??defaultOnMessage;return o.bot.onDirectMessage(async(e,t)=>{await dispatchMessage(o,s,e,t)}),o.bot.onNewMessage(/[\s\S]*/,async(e,t)=>{await dispatchMessage(o,s,e,t)}),o.channel}function defaultPhotonAuth(e){let t={};return e.author.userName!==void 0&&(t.user_name=e.author.userName),{attributes:t,authenticator:`photon-imessage`,issuer:`photon`,principalId:`photon:${e.author.userId}`,principalType:e.author.isBot?`service`:`user`,subject:e.author.userId}}async function defaultOnMessage(e,t){return{auth:defaultPhotonAuth(t)}}async function dispatchMessage(e,t,n,r){let a=await t({thread:n},r);if(a===null)return;await markReadBestEffort(e.bot.getAdapter(`imessage`),n,r);let o=photonInboundContent(r);o!==void 0&&await e.send({context:[...a.context??[]],message:o},{auth:a.auth,thread:n,title:a.title})}async function markReadBestEffort(e,t,n){try{await e.markRead(t.id,n.id)}catch{}}export{defaultPhotonAuth,photonIMessageChannel};
|
|
@@ -14,14 +14,17 @@
|
|
|
14
14
|
* handlers (`defaults.ts`) can all share the same low-level helpers
|
|
15
15
|
* without depending on each other.
|
|
16
16
|
*/
|
|
17
|
-
import { type SlackApiResponse as SlackPrimitiveApiResponse
|
|
17
|
+
import { type SlackApiResponse as SlackPrimitiveApiResponse } from "#compiled/@chat-adapter/slack/api.js";
|
|
18
18
|
import { type CardElement, type FileUpload } from "#compiled/chat/index.js";
|
|
19
|
+
/** Slack app installation workspace available when eve resolves a bot token. */
|
|
20
|
+
export interface SlackBotTokenContext {
|
|
21
|
+
readonly teamId?: string;
|
|
22
|
+
}
|
|
19
23
|
/**
|
|
20
24
|
* Slack bot token, materialized either as a literal `xoxb-...` string or
|
|
21
|
-
* as a (possibly async) function that
|
|
22
|
-
* supports secret-manager lookups and credential rotation.
|
|
25
|
+
* as a (possibly async) function that receives the app installation workspace.
|
|
23
26
|
*/
|
|
24
|
-
export type SlackBotToken =
|
|
27
|
+
export type SlackBotToken = string | ((context: SlackBotTokenContext) => string | Promise<string>);
|
|
25
28
|
/**
|
|
26
29
|
* Builds the channel-local continuation token (`<channelId>:<threadTs>`).
|
|
27
30
|
* The runtime's `send()` later namespaces it with the channel's
|
|
@@ -34,7 +37,7 @@ export declare function slackContinuationToken(channelId: string, threadTs: stri
|
|
|
34
37
|
* Materializes a {@link SlackBotToken} to a string, falling back to
|
|
35
38
|
* `process.env.SLACK_BOT_TOKEN`. Throws when neither is set.
|
|
36
39
|
*/
|
|
37
|
-
export declare function resolveSlackBotToken(token?: SlackBotToken): Promise<string>;
|
|
40
|
+
export declare function resolveSlackBotToken(token?: SlackBotToken, context?: SlackBotTokenContext): Promise<string>;
|
|
38
41
|
/**
|
|
39
42
|
* Slack Web API JSON response envelope. `ok` signals success, `error`
|
|
40
43
|
* carries Slack's error code on failure, and method-specific fields pass
|
|
@@ -49,6 +52,7 @@ export type SlackApiResponse = SlackPrimitiveApiResponse;
|
|
|
49
52
|
*/
|
|
50
53
|
export declare function callSlackApi(input: {
|
|
51
54
|
readonly botToken: SlackBotToken | undefined;
|
|
55
|
+
readonly context?: SlackBotTokenContext;
|
|
52
56
|
readonly operation: string;
|
|
53
57
|
readonly body: unknown;
|
|
54
58
|
}): Promise<SlackApiResponse>;
|
|
@@ -275,6 +279,9 @@ export interface SlackWorkspaceHandle {
|
|
|
275
279
|
/** Builds the workspace-scoped API handle used by generic event callbacks. */
|
|
276
280
|
export declare function buildSlackWorkspaceHandle(input: {
|
|
277
281
|
readonly botToken: SlackBotToken | undefined;
|
|
282
|
+
/** Workspace whose app installation supplies the bot token. */
|
|
283
|
+
readonly installationTeamId?: string;
|
|
284
|
+
/** Actor/content workspace exposed on the public handle. */
|
|
278
285
|
readonly teamId: string | undefined;
|
|
279
286
|
}): SlackWorkspaceHandle;
|
|
280
287
|
/**
|
|
@@ -303,6 +310,9 @@ export declare function buildSlackBinding(input: {
|
|
|
303
310
|
readonly botUserId?: string;
|
|
304
311
|
readonly channelId: string;
|
|
305
312
|
readonly threadTs: string;
|
|
313
|
+
/** Workspace whose app installation supplies the bot token. */
|
|
314
|
+
readonly installationTeamId?: string;
|
|
315
|
+
/** Actor/content workspace exposed on the public handle. */
|
|
306
316
|
readonly teamId: string | undefined;
|
|
307
317
|
readonly onThreadTsChanged?: (ts: string) => void;
|
|
308
318
|
}): SlackBinding;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{isCardElement}from"#compiled/chat/index.js";import{callSlackApi as callSlackApi$1,fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{cardToBlocks,cardToFallbackText}from"#public/channels/slack/blocks.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";const log=createLogger(`slack.api`);function slackContinuationToken(e,t){return`${e}:${t}`}async function resolveSlackBotToken(e){let
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{isCardElement}from"#compiled/chat/index.js";import{callSlackApi as callSlackApi$1,fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{cardToBlocks,cardToFallbackText}from"#public/channels/slack/blocks.js";import{resolveSlackInboundMrkdwn}from"#public/channels/slack/inbound-content.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";const log=createLogger(`slack.api`);function slackContinuationToken(e,t){return`${e}:${t}`}async function resolveSlackBotToken(e,t={}){let n=e??process.env.SLACK_BOT_TOKEN;if(!n)throw Error(`SLACK_BOT_TOKEN is required.`);return typeof n==`function`?n(t):resolveSlackBotToken$1(n)}async function callSlackApi(e){return callSlackApi$1(e.operation,normalizeSlackApiBody(e.body),createSlackApiOptions(e.botToken,e.context))}function createSlackRequester(e,t){return(n,r)=>callSlackApi({botToken:e,context:t,operation:n,body:r})}function buildSlackWorkspaceHandle(e){return{teamId:e.teamId,request:createSlackRequester(e.botToken,{teamId:e.installationTeamId})}}function buildSlackBinding(e){let n={teamId:e.installationTeamId},r=createSlackRequester(e.botToken,n),s=[],l=e.threadTs,u;function handleMessageTs(t){l||t===l||(l=t,e.onThreadTsChanged?.(t))}async function uploadFiles(t,r){let i=r?.channelId??e.channelId,a=r?.threadTs??l;return uploadSlackFiles(t.map(toSlackFileUpload),{...createSlackApiOptions(e.botToken,n),channelId:i||void 0,initialComment:r?.initialComment,threadTs:a||void 0})}function refreshMessages(){if(u!==void 0)return u;async function fetchAndReplace(){if(!e.channelId||!l){s=[];return}try{s=(await fetchSlackThreadReplies({...createSlackApiOptions(e.botToken,n),channel:e.channelId,limit:50,ts:l})).messages.map(t=>parseThreadMessage(t,l,{appId:e.appId,botUserId:e.botUserId}))}catch(n){logError(log,`refresh threw — swallowed`,n,{channelId:e.channelId})}}let r=fetchAndReplace().finally(()=>{u===r&&(u=void 0)});return u=r,r}let d={get recentMessages(){return s},async listParticipants(){await d.refresh();let e=new Set;for(let t of s)t.user!==void 0&&t.botId===void 0&&e.add(t.user);return[...e]},async post(t){let r=normalizePostInput(t),i=r.files??[],a=`blocks`in r||`card`in r||`markdown`in r;if(i.length>0&&!a){let e=await uploadFiles(i,{initialComment:`text`in r?r.text:void 0});return{id:Array.isArray(e.raw.files)&&e.raw.files.length>0?String(e.raw.files[0].id??``):``,raw:e.raw}}let s=await postSlackMessage(buildPostMessageOptions(r,e.channelId,l,e.botToken,n)),c=s.id;if(handleMessageTs(c),i.length>0&&a)try{await uploadFiles(i)}catch(e){log.warn(`file upload after message post failed`,{error:e})}return{id:c,raw:s.raw}},async postEphemeral(t,r){let i=await postSlackEphemeral({...buildPostMessageOptions(normalizePostInput(r),e.channelId,l,e.botToken,n),user:t});return{id:i.id,raw:i.raw}},async postDirectMessage(t,i){let a=await r(`conversations.open`,{users:t}),s=a.ok===!0?a.channel?.id:void 0;if(typeof s!=`string`||s.length===0)throw Error(`Slack conversations.open failed: ${a.error??`unknown_error`}`);let c=await postSlackMessage(buildPostMessageOptions(normalizePostInput(i),s,``,e.botToken,n));return{id:c.id,raw:c.raw}},async startTyping(n){if(!(!e.channelId||!l))try{let t=n===void 0?``:truncateTypingStatus(n),i={channel_id:e.channelId,thread_ts:l,status:t};t.length>0&&(i.loading_messages=[t]);let a=await r(`assistant.threads.setStatus`,i);a.ok!==!0&&log.warn(`assistant.threads.setStatus returned not-ok`,{error:a.error})}catch(n){logError(log,`startTyping threw — swallowed`,n,{channelId:e.channelId})}},refresh(){return refreshMessages()},mentionUser(e){return`<@${e}>`}};return{thread:d,slack:{channelId:e.channelId,get threadTs(){return l},teamId:e.teamId,request:r,uploadFiles}}}function normalizePostInput(e){return typeof e==`string`?{markdown:e}:isCardElement(e)?{card:e}:e}function buildPostMessageOptions(e,t,n,r,i){let a={...createSlackApiOptions(r,i),channel:t,threadTs:n||void 0,unfurlLinks:!1,unfurlMedia:!1};return`card`in e?(a.blocks=cardToBlocks(e.card),a.text=e.fallbackText??cardToFallbackText(e.card),a):`blocks`in e?(a.blocks=[...e.blocks],e.text!==void 0&&(a.text=e.text),a):`markdown`in e?(a.markdownText=e.markdown,a):(a.text=e.text,a)}function createSlackApiOptions(e,t={}){return{token:()=>resolveSlackBotToken(e,t)}}function normalizeSlackApiBody(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:{}}function toSlackFileUpload(e){return{data:normalizeFileData(e.data),filename:e.filename}}function normalizeFileData(e){if(e instanceof ArrayBuffer||typeof Blob<`u`&&e instanceof Blob)return e;if(ArrayBuffer.isView(e)){let t=e;return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}return e}function parseThreadMessage(e,t,n){let r=resolveSlackInboundMrkdwn(typeof e.text==`string`?e.text:``,e),i=typeof e.ts==`string`?e.ts:``,a=typeof e.thread_ts==`string`?e.thread_ts:t,o=typeof e.user==`string`?e.user:void 0,s=typeof e.bot_id==`string`?e.bot_id:void 0,c=typeof e.app_id==`string`?e.app_id:void 0,l=n.botUserId!==void 0&&o===n.botUserId||n.appId!==void 0&&c===n.appId;return{text:r,markdown:slackMrkdwnToGfm(r),user:o,botId:s,ts:i,threadTs:a,isMe:l,raw:e}}export{buildSlackBinding,buildSlackWorkspaceHandle,callSlackApi,resolveSlackBotToken,slackContinuationToken};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FilePart, UserContent } from "ai";
|
|
2
|
-
import type { FetchFileResult } from "#channel/adapter.js";
|
|
2
|
+
import type { FetchFileContext, FetchFileResult } from "#channel/adapter.js";
|
|
3
3
|
import { type SlackBotToken, type SlackThread } from "#public/channels/slack/api.js";
|
|
4
4
|
import type { SlackAttachment, SlackMessage } from "#public/channels/slack/inbound.js";
|
|
5
5
|
import type { UploadPolicy } from "#public/channels/upload-policy.js";
|
|
@@ -49,4 +49,4 @@ export declare function buildSlackTurnMessage(text: string, fileParts: readonly
|
|
|
49
49
|
*/
|
|
50
50
|
export declare function createSlackFetchFile(input: {
|
|
51
51
|
readonly botToken?: SlackBotToken;
|
|
52
|
-
}): (url: string) => Promise<FetchFileResult | null>;
|
|
52
|
+
}): (url: string, context?: FetchFileContext) => Promise<FetchFileResult | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{evaluateFilePart,formatUploadPolicyViolation,isUploadsDisabled}from"#public/channels/upload-policy.js";import{resolveSlackBotToken}from"#public/channels/slack/api.js";const log=createLogger(`slack.attachments`);function collectSlackFileParts(e,r){let i=[];for(let o of e??[]){let e=toSlackFilePart(o,i.length);if(e===null)continue;let s=evaluateFilePart(e,r);if(s!==null){log.warn(`dropped attachment — ${formatUploadPolicyViolation(s)}`,{name:o.name});continue}i.push(e)}return i}function toSlackFilePart(e,t){return e.type===`audio`||e.type===`video`?null:e.url?{type:`file`,mediaType:e.mimeType??`application/octet-stream`,filename:e.name??`attachment-${t}`,data:new URL(e.url)}:(log.warn(`dropped attachment — no url available`,{name:e.name}),null)}async function collectInboundFileParts(e){let t=collectSlackFileParts(e.mention.attachments,e.policy);if(t.length>0)return t;if(isUploadsDisabled(e.policy))return[];if(e.thread.recentMessages.length===0)try{await e.thread.refresh()}catch(e){return log.warn(`slack thread refresh failed for attachment collection`,{error:e}),[]}let n=e.thread.recentMessages;for(let t=n.length-1;t>=0;--t){let r=n[t];if(!r||r.isMe)continue;let i=r.raw,a=collectSlackFileParts(extractAttachmentsFromRaw(i?.files),e.policy);return a.length>0?a:[]}return[]}function extractAttachmentsFromRaw(e){return Array.isArray(e)?e.map(e=>{let t=typeof e.mimetype==`string`?e.mimetype:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:typeof e.url_private==`string`?e.url_private:void 0,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}):[]}function inferAttachmentType(e){return e===void 0?`file`:e.startsWith(`image/`)?`image`:e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:`file`}function buildSlackTurnMessage(e,t){return t.length===0?e:e.trim().length===0?[...t]:[{type:`text`,text:e},...t]}function createSlackFetchFile(e){return async
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{evaluateFilePart,formatUploadPolicyViolation,isUploadsDisabled}from"#public/channels/upload-policy.js";import{resolveSlackBotToken}from"#public/channels/slack/api.js";const log=createLogger(`slack.attachments`);function collectSlackFileParts(e,r){let i=[];for(let o of e??[]){let e=toSlackFilePart(o,i.length);if(e===null)continue;let s=evaluateFilePart(e,r);if(s!==null){log.warn(`dropped attachment — ${formatUploadPolicyViolation(s)}`,{name:o.name});continue}i.push(e)}return i}function toSlackFilePart(e,t){return e.type===`audio`||e.type===`video`?null:e.url?{type:`file`,mediaType:e.mimeType??`application/octet-stream`,filename:e.name??`attachment-${t}`,data:new URL(e.url)}:(log.warn(`dropped attachment — no url available`,{name:e.name}),null)}async function collectInboundFileParts(e){let t=collectSlackFileParts(e.mention.attachments,e.policy);if(t.length>0)return t;if(isUploadsDisabled(e.policy))return[];if(e.thread.recentMessages.length===0)try{await e.thread.refresh()}catch(e){return log.warn(`slack thread refresh failed for attachment collection`,{error:e}),[]}let n=e.thread.recentMessages;for(let t=n.length-1;t>=0;--t){let r=n[t];if(!r||r.isMe)continue;let i=r.raw,a=collectSlackFileParts(extractAttachmentsFromRaw(i?.files),e.policy);return a.length>0?a:[]}return[]}function extractAttachmentsFromRaw(e){return Array.isArray(e)?e.map(e=>{let t=typeof e.mimetype==`string`?e.mimetype:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:typeof e.url_private==`string`?e.url_private:void 0,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}):[]}function inferAttachmentType(e){return e===void 0?`file`:e.startsWith(`image/`)?`image`:e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:`file`}function buildSlackTurnMessage(e,t){return t.length===0?e:e.trim().length===0?[...t]:[{type:`text`,text:e},...t]}function createSlackFetchFile(e){return async(t,n)=>{if(!isSlackFileUrl(t))return null;let r=n?.state.installationTeamId,a=await resolveSlackBotToken(e.botToken,{teamId:typeof r==`string`?r:void 0}),o=await fetch(t,{headers:{authorization:`Bearer ${a}`}});if(!o.ok)throw Error(`Slack file fetch returned HTTP ${o.status} for ${t}.`);let s=o.headers.get(`content-type`)??void 0;if(s?.split(`;`,1)[0]?.trim().toLowerCase()===`text/html`)throw Error(`Slack file fetch returned an HTML sign-in page instead of file bytes for ${t}. The bot token may be missing the files:read scope. Add the scope, reinstall the Slack app, and retry.`);return{bytes:Buffer.from(await o.arrayBuffer()),mediaType:s}}}function isSlackFileUrl(e){let t=URL.parse(e);return t?.protocol===`https:`?t.hostname===`files.slack.com`||(t.hostname===`enterprise.slack.com`||t.hostname.endsWith(`.enterprise.slack.com`))&&t.pathname.startsWith(`/files/`):!1}export{buildSlackTurnMessage,collectInboundFileParts,collectSlackFileParts,createSlackFetchFile};
|
|
@@ -119,6 +119,8 @@ export declare function formatInputRequestFallbackText(request: InputRequest): s
|
|
|
119
119
|
export interface HitlFreeformModalMetadata {
|
|
120
120
|
readonly continuationToken: string;
|
|
121
121
|
readonly channelId: string;
|
|
122
|
+
/** Workspace whose app installation supplies credentials for modal follow-up API calls. */
|
|
123
|
+
readonly installationTeamId?: string;
|
|
122
124
|
readonly threadTs: string;
|
|
123
125
|
readonly messageTs: string;
|
|
124
126
|
readonly requestId: string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Derives inbound mrkdwn from top-level text plus Block Kit and legacy attachments. */
|
|
2
|
+
export declare function resolveSlackInboundMrkdwn(text: string, raw: Record<string, unknown>): string;
|
|
3
|
+
/** Reads the `text` string from a Slack composition text object (`{ text: "..." }`). */
|
|
4
|
+
export declare function readSlackTextObject(textObject: unknown): string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{isObject}from"#shared/guards.js";import{formatSlackLink}from"#compiled/@chat-adapter/slack/format.js";function resolveSlackInboundMrkdwn(e,t){try{return resolveSlackInboundMrkdwnUnsafe(e,t)}catch{return e}}function resolveSlackInboundMrkdwnUnsafe(e,t){let n=extractSlackStructuredMrkdwn(t.blocks,t.attachments),r=e.trim();if(!r)return n;if(!n||normalizeComparableText(n)===normalizeComparableText(r))return e;let i=normalizeComparableText(n),a=normalizeComparableText(r);return n.length>r.length&&i.includes(a)?n:n.length>=r.length*2?Array.isArray(t.attachments)&&t.attachments.length>0&&!i.includes(a)?`${e}\n${n}`:n:e}function extractSlackStructuredMrkdwn(e,t){return[...extractBlockKitLines(e),...extractLegacyAttachmentLines(t)].join(`
|
|
2
|
+
`).trim()}function extractBlockKitLines(t){if(!Array.isArray(t))return[];let n=[];for(let r of t)if(isObject(r))switch(r.type){case`section`:appendTextObjectLine(n,r.text),appendFieldLines(n,r.fields),appendSectionAccessoryLine(n,r.accessory);break;case`header`:appendTextObjectLine(n,r.text);break;case`video`:appendTextObjectLine(n,r.title),appendTextObjectLine(n,r.description);break;case`markdown`:typeof r.text==`string`&&r.text.length>0&&n.push(r.text);break;case`table`:case`data_table`:appendTableLines(n,r);break;case`card`:appendCardLines(n,r);break;case`carousel`:if(Array.isArray(r.elements))for(let t of r.elements)isObject(t)&&appendCardLines(n,t);break;case`container`:appendTextObjectLine(n,r.title),appendTextObjectLine(n,r.subtitle),n.push(...extractBlockKitLines(r.child_blocks));break;case`context`:appendElementLines(n,r.elements);break;case`actions`:appendActionsLine(n,r.elements);break;case`rich_text`:appendRichTextLine(n,r.elements);break;case`image`:typeof r.alt_text==`string`&&r.alt_text.length>0&&n.push(r.alt_text),typeof r.title==`object`&&r.title!==null&&appendTextObjectLine(n,r.title);break;default:break}return n}function extractLegacyAttachmentLines(t){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!isObject(r))continue;let t=n.length;typeof r.pretext==`string`&&r.pretext.length>0&&n.push(r.pretext),typeof r.title==`string`&&r.title.length>0&&n.push(r.title),typeof r.text==`string`&&r.text.length>0&&n.push(r.text),appendLegacyFieldLines(n,r.fields),typeof r.footer==`string`&&r.footer.length>0&&n.push(r.footer),n.push(...extractBlockKitLines(r.blocks)),n.length===t&&typeof r.fallback==`string`&&r.fallback.length>0&&n.push(r.fallback)}return n}function appendTextObjectLine(e,t){let n=readSlackTextObject(t);n&&e.push(n)}function appendFieldLines(e,t){if(Array.isArray(t))for(let n of t)appendTextObjectLine(e,n)}function appendElementLines(t,n){if(Array.isArray(n)){for(let r of n)if(isObject(r)){if(r.type===`image`){typeof r.alt_text==`string`&&r.alt_text.length>0&&t.push(r.alt_text);continue}appendTextObjectLine(t,r)}}}function appendSectionAccessoryLine(t,n){isObject(n)&&n.type===`button`&&appendActionsLine(t,[n])}function appendCardLines(e,t){appendTextObjectLine(e,t.title),appendTextObjectLine(e,t.subtitle),appendTextObjectLine(e,t.body),appendTextObjectLine(e,t.subtext),appendActionsLine(e,t.actions)}function appendTableLines(e,t){if(appendTextObjectLine(e,t.caption),Array.isArray(t.rows))for(let n of t.rows){if(!Array.isArray(n))continue;let t=n.map(tableCellToPlain);t.some(e=>e.length>0)&&e.push(t.join(` | `))}}function tableCellToPlain(t){return isObject(t)?t.type===`rich_text`?richTextPartsToPlain(t.elements):typeof t.text==`string`?t.text:typeof t.value==`number`||typeof t.value==`string`?String(t.value):``:``}function appendActionsLine(t,n){if(!Array.isArray(n))return;let r=n.map(t=>isObject(t)?readSlackTextObject(t.text):``).filter(e=>e.length>0);r.length>0&&t.push(r.map(e=>`[${e}]`).join(` `))}function appendLegacyFieldLines(t,n){if(Array.isArray(n))for(let r of n){if(!isObject(r))continue;let n=typeof r.title==`string`?r.title.trim():``,i=typeof r.value==`string`?r.value.trim():``;n&&i?t.push(`${n}: ${i}`):i?t.push(i):n&&t.push(n)}}function appendRichTextLine(e,t){let n=richTextPartsToPlain(t);n&&e.push(n)}function richTextPartsToPlain(t){return Array.isArray(t)?t.map(t=>isObject(t)?richTextPartToPlain(t):``).filter(e=>e.length>0).join(`
|
|
3
|
+
`):``}function richTextPartToPlain(e){switch(e.type){case`rich_text_section`:case`rich_text_preformatted`:case`rich_text_quote`:return richTextInlineToPlain(e.elements);case`rich_text_list`:return richTextListToPlain(e);default:return``}}function richTextInlineToPlain(t){if(!Array.isArray(t))return``;let n=[];for(let r of t)if(isObject(r))switch(r.type){case`text`:typeof r.text==`string`&&n.push(r.text);break;case`link`:typeof r.url==`string`&&typeof r.text==`string`?n.push(formatInboundRichTextLink(r.url,r.text)):typeof r.text==`string`?n.push(r.text):typeof r.url==`string`&&n.push(r.url);break;case`emoji`:typeof r.name==`string`&&n.push(`:${r.name}:`);break;case`user`:typeof r.user_id==`string`&&n.push(`<@${r.user_id}>`);break;case`channel`:typeof r.channel_id==`string`&&n.push(`<#${r.channel_id}>`);break;case`usergroup`:typeof r.usergroup_id==`string`&&n.push(`<!subteam^${r.usergroup_id}>`);break;case`broadcast`:typeof r.range==`string`&&n.push(`<!${r.range}>`);break;case`date`:typeof r.fallback==`string`&&n.push(r.fallback);break;default:break}return n.join(``)}function richTextListToPlain(t){return Array.isArray(t.elements)?t.elements.map(t=>isObject(t)?richTextInlineToPlain(t.elements):``).filter(e=>e.length>0).join(`
|
|
4
|
+
`):``}function readSlackTextObject(t){return isObject(t)&&typeof t.text==`string`?t.text:``}function normalizeComparableText(e){return e.replace(/\s+/gu,` `).trim()}function formatInboundRichTextLink(e,n){try{return formatSlackLink(e,n)}catch(t){if(t instanceof TypeError)return`${n} (${e})`;throw t}}export{readSlackTextObject,resolveSlackInboundMrkdwn};
|
|
@@ -45,7 +45,11 @@ export interface SlackAttachment {
|
|
|
45
45
|
* `onAppMention(ctx, message)`).
|
|
46
46
|
*/
|
|
47
47
|
export interface SlackMessage {
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The message body (mrkdwn). Usually the top-level Slack `text`; when
|
|
50
|
+
* that is empty or a short fallback, derived from Block Kit blocks and
|
|
51
|
+
* legacy attachments on the raw event.
|
|
52
|
+
*/
|
|
49
53
|
readonly text: string;
|
|
50
54
|
/** {@link text} re-rendered as GFM markdown for the agent. */
|
|
51
55
|
readonly markdown: string;
|
|
@@ -83,6 +87,7 @@ export interface SlackEventCallback {
|
|
|
83
87
|
readonly team_id?: string;
|
|
84
88
|
readonly authorizations?: readonly {
|
|
85
89
|
readonly is_bot?: boolean;
|
|
90
|
+
readonly team_id?: string;
|
|
86
91
|
readonly user_id?: string;
|
|
87
92
|
}[];
|
|
88
93
|
readonly event?: {
|
|
@@ -128,6 +133,8 @@ export declare function parseAppMentionEvent(envelope: SlackEventCallback): Slac
|
|
|
128
133
|
*/
|
|
129
134
|
/** Returns the bot user id attached to a Slack event authorization. */
|
|
130
135
|
export declare function slackEventBotUserId(envelope: SlackEventCallback): string | undefined;
|
|
136
|
+
/** Returns the workspace whose app installation authorized this event. */
|
|
137
|
+
export declare function slackEventInstallationTeamId(envelope: SlackEventCallback): string | undefined;
|
|
131
138
|
/** Parses a Slack message event without applying bot or subtype policy. */
|
|
132
139
|
export declare function parseMessageEvent(envelope: SlackEventCallback): SlackMessage | null;
|
|
133
140
|
export declare function parseDirectMessageEvent(envelope: SlackEventCallback): SlackMessage | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isObject}from"#shared/guards.js";import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";function parseSlackEventEnvelope(t){let n=JSON.parse(t);if(!isObject(n)||n.type!==`event_callback`)return null;let r=n.event;return!isObject(r)||typeof r.type!=`string`||r.type.length===0?null:n}function parseAppMentionEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`app_mention`?null:buildSlackMessage(t,e.team_id)}function slackEventBotUserId(e){return e.authorizations?.find(e=>e.is_bot===!0&&typeof e.user_id==`string`)?.user_id}function parseMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`message`?null:buildSlackMessage(t,e.team_id)}function parseDirectMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;if(!t||t.type!==`message`)return null;let n=t;return n.channel_type!==`im`||!isHumanMessage(n)?null:buildSlackMessage(n,e.team_id)}function isHumanMessage(e){return typeof e.subtype==`string`&&e.subtype.length>0&&e.subtype!==`file_share`?!1:!(typeof e.bot_id==`string`&&e.bot_id.length>0)}function slackMessageFromWebhookPayload(e){
|
|
1
|
+
import{isObject}from"#shared/guards.js";import{resolveSlackInboundMrkdwn}from"#public/channels/slack/inbound-content.js";import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";function parseSlackEventEnvelope(t){let n=JSON.parse(t);if(!isObject(n)||n.type!==`event_callback`)return null;let r=n.event;return!isObject(r)||typeof r.type!=`string`||r.type.length===0?null:n}function parseAppMentionEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`app_mention`?null:buildSlackMessage(t,e.team_id)}function slackEventBotUserId(e){return e.authorizations?.find(e=>e.is_bot===!0&&typeof e.user_id==`string`)?.user_id}function slackEventInstallationTeamId(e){let t=e.authorizations??[],n=t.find(e=>e.is_bot===!0&&typeof e.team_id==`string`);return n?.team_id===void 0?t.find(e=>typeof e.team_id==`string`)?.team_id:n.team_id}function parseMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`message`?null:buildSlackMessage(t,e.team_id)}function parseDirectMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;if(!t||t.type!==`message`)return null;let n=t;return n.channel_type!==`im`||!isHumanMessage(n)?null:buildSlackMessage(n,e.team_id)}function isHumanMessage(e){return typeof e.subtype==`string`&&e.subtype.length>0&&e.subtype!==`file_share`?!1:!(typeof e.bot_id==`string`&&e.bot_id.length>0)}function slackMessageFromWebhookPayload(e){if(e.kind===`direct_message`&&(typeof e.subtype==`string`&&e.subtype.length>0&&e.subtype!==`file_share`||typeof e.botId==`string`&&e.botId.length>0)||!e.channelId||!e.ts)return null;let r=resolveSlackInboundMrkdwn(e.text,e.raw);return{text:r,markdown:slackMrkdwnToGfm(r),ts:e.ts,threadTs:e.threadTs,channelId:e.channelId,teamId:e.teamId,author:parsePayloadAuthor(e),attachments:parsePayloadAttachments(e.files),raw:e.raw}}function buildSlackMessage(e,r){let i=typeof e.channel==`string`?e.channel:``,a=typeof e.ts==`string`?e.ts:``;if(!i||!a)return null;let o=resolveSlackInboundMrkdwn(typeof e.text==`string`?e.text:``,e),s=typeof e.thread_ts==`string`?e.thread_ts:a,c=typeof r==`string`?r:void 0;return{text:o,markdown:slackMrkdwnToGfm(o),ts:a,threadTs:s,channelId:i,teamId:c,author:parseAuthor(e),attachments:parseAttachments(e.files),raw:e}}function parseAuthor(e){let t=typeof e.user==`string`?e.user:``;if(t)return{userId:t,userName:typeof e.username==`string`?e.username:void 0,fullName:void 0,isBot:typeof e.bot_id==`string`&&e.bot_id.length>0,isMe:!1}}function parseAttachments(e){return Array.isArray(e)?e.map(toAttachment):[]}function toAttachment(e){let t=typeof e.mimetype==`string`?e.mimetype:void 0,n=typeof e.url_private==`string`?e.url_private:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:n,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}function toPayloadAttachment(e){return{id:e.id,type:e.type,url:e.url,name:e.name,mimeType:e.mimeType,size:e.size}}function inferAttachmentType(e){return e===void 0?`file`:e.startsWith(`image/`)?`image`:e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:`file`}function parsePayloadAuthor(e){if(!e.userId)return;let t=e.raw;return{userId:e.userId,userName:typeof t.username==`string`?t.username:void 0,fullName:void 0,isBot:typeof t.bot_id==`string`&&t.bot_id.length>0,isMe:!1}}function parsePayloadAttachments(e){return Array.isArray(e)?e.map(toPayloadAttachment):[]}export{parseAppMentionEvent,parseDirectMessageEvent,parseMessageEvent,parseSlackEventEnvelope,slackEventBotUserId,slackEventInstallationTeamId,slackMessageFromWebhookPayload};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export type { ModelMessage } from "ai";
|
|
8
8
|
export { slackChannel, type SlackApiResponse, type SlackAuthorizationEventContext, type SlackAuthorizationRequiredHandler, type SlackBotToken, type SlackChannel, type SlackChannelConfig, type SlackChannelCredentials, type SlackChannelEvents, type SlackChannelState, type SlackContext, type SlackEventContext, type SlackEventRespondFn, type SlackEventRespondOptions, type SlackEventSendFn, type SlackEventSendOptions, type SlackHandle, type SlackInboundEventContext, type SlackInboundMessageContext, type SlackInboundResult, type SlackInboundResultOrPromise, type SlackInitialMessage, type SlackInputResponseContext, type SlackInputResponseResult, type SlackInputResponseSubmission, type SlackInstrumentationMetadata, type SlackInteractionAction, type SlackInteractionContext, type SlackMentionResult, type SlackMentionResultOrPromise, type SlackReceiveTarget, type SlackSessionTarget, type SlackRespondOptions, type SlackSendOptions, type SlackSessionOperations, type SlackThread, type SlackWebhookVerifier, type SlackWorkspaceHandle, } from "#public/channels/slack/slackChannel.js";
|
|
9
9
|
export type { SlackAttachment, SlackAuthor, SlackEvent, SlackEventEnvelope, SlackInboundContext, SlackMessage, } from "#public/channels/slack/inbound.js";
|
|
10
|
-
export { callSlackApi, resolveSlackBotToken, slackContinuationToken, type SlackPostInput, type SlackPostedMessage, type SlackThreadMessage, type SlackUploadFilesOptions, type SlackUploadFilesResult, } from "#public/channels/slack/api.js";
|
|
10
|
+
export { callSlackApi, resolveSlackBotToken, slackContinuationToken, type SlackPostInput, type SlackPostedMessage, type SlackBotTokenContext, type SlackThreadMessage, type SlackUploadFilesOptions, type SlackUploadFilesResult, } from "#public/channels/slack/api.js";
|
|
11
11
|
export { defaultSlackAuth } from "#public/channels/slack/defaults.js";
|
|
12
12
|
export { describeActionRequest, describeActionRequests, } from "#public/channels/slack/action-status.js";
|
|
13
13
|
export { loadThreadContextMessages, type LoadThreadContextMessagesOptions, type ThreadContextSince, } from "#public/channels/slack/thread.js";
|
|
@@ -9,6 +9,7 @@ export declare function authorizeInputResponse(input: {
|
|
|
9
9
|
readonly config: SlackChannelConfig;
|
|
10
10
|
readonly onInputResponse: NonNullable<SlackChannelConfig["onInputResponse"]>;
|
|
11
11
|
};
|
|
12
|
+
readonly installationTeamId: string | null | undefined;
|
|
12
13
|
readonly submission: SlackInputResponseSubmission;
|
|
13
14
|
readonly teamId: string | null | undefined;
|
|
14
15
|
readonly threadTs: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{buildSlackBinding}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{deriveHitlResponse}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.interactions`);function approvalResponderStatePatch(e,t){if(!(t?.principalId===void 0||!e.actions.some(e=>deriveHitlResponse(e)?.kind===`tool-approval`)))return{approvalResponderUsers:{[t.principalId]:e.user.id}}}async function authorizeInputResponse(e){let n=buildSlackAuthContext({channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,userId:e.submission.user.id,userName:e.submission.user.username??e.submission.user.name}),{thread:r,slack:i}=buildSlackBinding({botToken:e.deps.config.credentials?.botToken,channelId:e.channelId,threadTs:e.threadTs,teamId:e.teamId??void 0}),a={defaultAuth:n,slack:i,thread:r};try{return await e.deps.onInputResponse(a,e.submission)}catch(e){return log.error(`HITL input response authorization failed`,{error:e}),null}}export{approvalResponderStatePatch,authorizeInputResponse};
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{buildSlackBinding}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{deriveHitlResponse}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.interactions`);function approvalResponderStatePatch(e,t){if(!(t?.principalId===void 0||!e.actions.some(e=>deriveHitlResponse(e)?.kind===`tool-approval`)))return{approvalResponderUsers:{[t.principalId]:e.user.id}}}async function authorizeInputResponse(e){let n=buildSlackAuthContext({channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,userId:e.submission.user.id,userName:e.submission.user.username??e.submission.user.name}),{thread:r,slack:i}=buildSlackBinding({botToken:e.deps.config.credentials?.botToken,channelId:e.channelId,threadTs:e.threadTs,installationTeamId:e.installationTeamId??void 0,teamId:e.teamId??void 0}),a={defaultAuth:n,slack:i,thread:r};try{return await e.deps.onInputResponse(a,e.submission)}catch(e){return log.error(`HITL input response authorization failed`,{error:e}),null}}export{approvalResponderStatePatch,authorizeInputResponse};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { InteractionHandlerDeps, ParsedBlockActionsPayload } from "#public/channels/slack/interactions.js";
|
|
2
|
+
export declare function updateAnsweredHitlCard(interaction: ParsedBlockActionsPayload, deps: InteractionHandlerDeps): Promise<void>;
|
|
3
|
+
export declare function updateAnsweredFreeformCard(input: {
|
|
4
|
+
readonly channelId: string;
|
|
5
|
+
readonly messageTs: string;
|
|
6
|
+
readonly answerLabel: string;
|
|
7
|
+
readonly userId?: string;
|
|
8
|
+
readonly installationTeamId?: string;
|
|
9
|
+
readonly deps: InteractionHandlerDeps;
|
|
10
|
+
}): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SLACK_CARD_SUBTEXT_MAX_LENGTH,truncateCardSubtext}from"#public/channels/slack/limits.js";import{resolveSlackBotToken}from"#public/channels/slack/api.js";import{buildAnsweredBlocks,isHitlAction}from"#public/channels/slack/hitl.js";function buildAnsweredHitlMessageBlocks(e){let t=e.messageBlocks.findIndex(t=>blockContainsActionId(t,e.actionId));if(t===-1)return buildAnsweredBlocks({promptBlocks:findPromptBlocks(e.messageBlocks),answerLabel:e.answerLabel,userId:e.userId});let n=e.messageBlocks[t],i=answeredBlocksFromActionBlock({answerLabel:e.answerLabel,block:n,userId:e.userId})??buildAnsweredBlocks({promptBlocks:promptBlocksFromActionBlock(n),answerLabel:e.answerLabel,userId:e.userId});return[...e.messageBlocks.slice(0,t),...i,...e.messageBlocks.slice(t+1)]}function findPromptBlocks(e){let t=[];for(let n of e){if(!isObjectRecord(n))continue;let e=n.type;if(e===`actions`)break;(e===`section`||e===`context`||e===`divider`||e===`image`)&&t.push(n)}return t}function blockContainsActionId(e,t){return isObjectRecord(e)?actionsContainActionId(e.elements,t)||actionsContainActionId(e.actions,t):!1}function actionsContainActionId(e,t){return Array.isArray(e)?e.some(e=>isObjectRecord(e)&&e.action_id===t):!1}function answeredBlocksFromActionBlock(e){if(!isObjectRecord(e.block)||e.block.type!==`card`)return;let{actions:t,subtext:n,...r}=e.block,i={...r,subtext:{type:`mrkdwn`,text:formatAnsweredCardSubtext(e),verbatim:!1}};return hasCardContent(i)?[i]:void 0}function formatAnsweredCardSubtext(n){let r=n.userId.length>0?` by <@${n.userId}>`:``,i=SLACK_CARD_SUBTEXT_MAX_LENGTH-20-1-r.length;return truncateCardSubtext(`:white_check_mark: *${truncateWithEllipsis(n.answerLabel,i)}*${r}`)}function truncateWithEllipsis(e,t){if(t<=0)return``;if(e.length<=t)return e;let n=Math.max(0,t-3);return`${e.slice(0,n).trimEnd()}...`}function promptBlocksFromActionBlock(e){if(!isObjectRecord(e)||e.type!==`card`)return[];let{actions:t,...n}=e;return hasCardContent(n)?[n]:[]}function hasCardContent(e){return e.body!==void 0||e.title!==void 0||e.hero_image!==void 0}function isObjectRecord(e){return typeof e==`object`&&!!e}async function updateAnsweredHitlCard(e,t){let n=e.actions.find(e=>isHitlAction(e.actionId));if(!n?.messageTs)return;let r=n.label??n.selectedOptionValue??n.value;r&&await updateAnsweredCard({answerLabel:r,blocks:buildAnsweredHitlMessageBlocks({actionId:n.actionId,answerLabel:r,messageBlocks:e.messageBlocks,userId:n.user.id}),channelId:e.channelId,deps:t,installationTeamId:e.installationTeamId,messageTs:n.messageTs})}async function updateAnsweredFreeformCard(e){await updateAnsweredCard({...e,blocks:buildAnsweredBlocks({promptBlocks:[],answerLabel:e.answerLabel,userId:e.userId})})}async function updateAnsweredCard(e){let t=await resolveSlackBotToken(e.deps.config.credentials?.botToken,{teamId:e.installationTeamId}),r=await fetch(`https://slack.com/api/chat.update`,{method:`POST`,headers:{authorization:`Bearer ${t}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({channel:e.channelId,ts:e.messageTs,blocks:e.blocks,text:`Answered: ${e.answerLabel}`})});if(!r.ok)throw Error(`Slack chat.update returned HTTP ${r.status}`)}export{updateAnsweredFreeformCard,updateAnsweredHitlCard};
|
|
@@ -13,6 +13,7 @@ import type { ChannelFrom, ChannelResolveSession } from "#channel/channel-operat
|
|
|
13
13
|
interface ParsedBlockActionsPayload {
|
|
14
14
|
readonly actions: SlackInteractionAction[];
|
|
15
15
|
readonly channelId: string;
|
|
16
|
+
readonly installationTeamId: string | undefined;
|
|
16
17
|
readonly threadTs: string;
|
|
17
18
|
readonly teamId: string | undefined;
|
|
18
19
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{parseInputResponse}from"#runtime/input/types.js";import{SLACK_CARD_SUBTEXT_MAX_LENGTH,truncateCardSubtext}from"#public/channels/slack/limits.js";import{buildSlackBinding,resolveSlackBotToken,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{HITL_FREEFORM_MODAL_ACTION_ID,HITL_FREEFORM_MODAL_BLOCK_ID,HITL_FREEFORM_MODAL_CALLBACK_ID,buildAnsweredBlocks,buildFreeformModalView,deriveHitlResponse,freeformRequestIdFromActionId,isFreeformAction,isHitlAction}from"#public/channels/slack/hitl.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{approvalResponderStatePatch,authorizeInputResponse}from"#public/channels/slack/input-response.js";import{bindSlackSessionOperations}from"#public/channels/slack/session-operations.js";const log=createLogger(`slack.interactions`);function parseBlockActionsPayload(e){if(isSharedBlockActionsPayload(e))return parseSharedBlockActionsPayload(e);let t=e,n=t.actions;if(!Array.isArray(n))return null;let r=t.channel?.id,i=t.message,a=i?.thread_ts??i?.ts;if(!r||!a)return null;let o=t.team,s=t.user,c=o?.id??s.team_id,l={id:s.id,username:s.username,name:s.name},u=i?.blocks??[];return{actions:n.map(e=>({actionId:String(e.action_id??``),value:e.value==null?void 0:String(e.value),blockId:e.block_id==null?void 0:String(e.block_id),selectedOptionValue:extractSelectedOptionValue(e),messageTs:i?.ts,label:extractActionLabel(e),user:l})),channelId:r,threadTs:a,teamId:c,messageBlocks:u}}function isSharedBlockActionsPayload(e){return e.kind===`block_actions`&&Array.isArray(e.actions)}function parseSharedBlockActionsPayload(e){return!e.channelId||!e.threadTs?null:{actions:e.actions.map(t=>({actionId:t.actionId,value:t.value,blockId:t.blockId,selectedOptionValue:t.selectedOptionValue,messageTs:e.messageTs,label:t.label,user:{id:t.user?.id??e.userId,username:t.user?.username,name:t.user?.name}})),channelId:e.channelId,threadTs:e.threadTs,teamId:e.teamId,messageBlocks:e.messageBlocks??[]}}function extractSelectedOptionValue(e){let t=e.selected_option;return typeof t?.value==`string`?t.value:void 0}function extractActionLabel(e){let t=e.selected_option?.text?.text;if(typeof t==`string`&&t.length>0)return t;let n=e.text?.text;if(typeof n==`string`&&n.length>0)return n}function findPromptBlock(e){return findPromptBlocks(e)[0]}function findPromptBlocks(e){let t=[];for(let n of e){if(typeof n!=`object`||!n)continue;let e=n.type;if(e===`actions`)break;(e===`section`||e===`context`||e===`divider`||e===`image`)&&t.push(n)}return t}function readPromptTextFromBlocks(e){let t=findPromptBlock(e)?.text?.text;return typeof t==`string`&&t.length>0?t:void 0}function buildAnsweredHitlMessageBlocks(e){let t=findActionBlockIndex(e.messageBlocks,e.actionId);if(t===-1)return buildAnsweredBlocks({promptBlocks:findPromptBlocks(e.messageBlocks),answerLabel:e.answerLabel,userId:e.userId});let n=e.messageBlocks[t],r=answeredBlocksFromActionBlock({answerLabel:e.answerLabel,block:n,userId:e.userId})??buildAnsweredBlocks({promptBlocks:promptBlocksFromActionBlock(n),answerLabel:e.answerLabel,userId:e.userId});return[...e.messageBlocks.slice(0,t),...r,...e.messageBlocks.slice(t+1)]}function findActionBlockIndex(e,t){return e.findIndex(e=>blockContainsActionId(e,t))}function blockContainsActionId(e,t){return isObjectRecord(e)?actionsContainActionId(e.elements,t)||actionsContainActionId(e.actions,t):!1}function actionsContainActionId(e,t){return Array.isArray(e)?e.some(e=>isObjectRecord(e)&&e.action_id===t):!1}function isObjectRecord(e){return typeof e==`object`&&!!e}function answeredBlocksFromActionBlock(e){if(!isObjectRecord(e.block)||e.block.type!==`card`)return;let{actions:t,subtext:n,...r}=e.block,i={...r,subtext:{type:`mrkdwn`,text:formatAnsweredCardSubtext(e),verbatim:!1}};return hasCardContent(i)?[i]:void 0}function formatAnsweredCardSubtext(e){let t=e.userId.length>0?` by <@${e.userId}>`:``,i=SLACK_CARD_SUBTEXT_MAX_LENGTH-20-1-t.length;return truncateCardSubtext(`:white_check_mark: *${truncateWithEllipsis(e.answerLabel,i)}*${t}`)}function truncateWithEllipsis(e,t){if(t<=0)return``;if(e.length<=t)return e;let n=Math.max(0,t-3);return`${e.slice(0,n).trimEnd()}...`}function promptBlocksFromActionBlock(e){if(!isObjectRecord(e)||e.type!==`card`)return[];let{actions:t,...n}=e;return hasCardContent(n)?[n]:[]}function hasCardContent(e){return e.body!==void 0||e.title!==void 0||e.hero_image!==void 0}async function handleInteractionPost(e,t,n){let r=new Response(`ok`,{status:200}),a;try{a=parseSlackWebhookBody(e,{contentType:`application/x-www-form-urlencoded`})}catch{return log.warn(`failed to parse Slack interaction payload`),r}if(a.kind===`view_submission`)return handleViewSubmission(a,t,n);if(a.kind!==`block_actions`)return r;let c=parseBlockActionsPayload(a);if(!c)return r;let l=c.actions.find(e=>isFreeformAction(e.actionId));if(l)return await openFreeformModal({payload:a.raw,interaction:c,freeformAction:l,deps:n}),r;let u=slackContinuationToken(c.channelId,c.threadTs),d=c.actions.flatMap(e=>{let t=deriveHitlResponse(e);return t===null?[]:[{action:e,derived:t}]});if(d.length>0){let e=d[0].action.user;t.waitUntil(dispatchBlockInputResponses({ctx:t,deps:n,interaction:c,submission:{type:`block_actions`,actions:d.map(({action:e})=>e),inputResponses:d.map(({derived:e})=>e.response),messageTs:d[0].action.messageTs,user:e}}))}let f=n.config.onInteraction;if(f){let e=c.actions.filter(e=>!isHitlAction(e.actionId));if(e.length>0){let r=e[0].user,{thread:a,slack:o}=buildSlackBinding({botToken:n.config.credentials?.botToken,channelId:c.channelId,threadTs:c.threadTs,teamId:c.teamId}),l={...bindSlackSessionOperations({address:u,defaultAuth:buildSlackAuthContext({channelId:c.channelId,teamId:c.teamId,threadTs:c.threadTs,userId:r.id,userName:r.username??r.name}),from:t.from,resolveSession:t.resolveSession,state:{channelId:c.channelId,teamId:c.teamId??null,threadTs:c.threadTs,triggeringUserId:r.id}}),thread:a,slack:o};for(let n of e)t.waitUntil(Promise.resolve(f(n,l)).catch(e=>{log.error(`custom interaction handler failed`,{error:e})}))}}return r}async function dispatchBlockInputResponses(e){let t=await authorizeInputResponse({channelId:e.interaction.channelId,deps:e.deps,submission:e.submission,teamId:e.interaction.teamId,threadTs:e.interaction.threadTs});if(t!==null){try{await e.ctx.from(slackContinuationToken(e.interaction.channelId,e.interaction.threadTs)).respond(e.submission.inputResponses,{auth:t.auth,state:approvalResponderStatePatch(e.submission,t.auth)})}catch(e){log.error(`HITL interaction delivery failed`,{error:e});return}if(!e.submission.actions.some(e=>deriveHitlResponse(e)?.kind===`tool-approval`))try{await updateAnsweredHitlCard(e.interaction,e.deps)}catch(e){log.error(`HITL answered-card update failed`,{error:e})}}}async function openFreeformModal(e){let t=e.payload.trigger_id;if(typeof t!=`string`||t.length===0){log.warn(`freeform button click missing trigger_id — cannot open modal`);return}let n=freeformRequestIdFromActionId(e.freeformAction.actionId)??e.freeformAction.value;if(!n){log.warn(`freeform button click missing requestId`);return}let r=e.freeformAction.messageTs;if(!r){log.warn(`freeform button click missing messageTs`);return}let i=buildFreeformModalView({metadata:{continuationToken:slackContinuationToken(e.interaction.channelId,e.interaction.threadTs),channelId:e.interaction.channelId,threadTs:e.interaction.threadTs,messageTs:r,requestId:n},prompt:readPromptTextFromBlocks(e.interaction.messageBlocks)}),s=await resolveSlackBotToken(e.deps.config.credentials?.botToken),c=await fetch(`https://slack.com/api/views.open`,{method:`POST`,headers:{authorization:`Bearer ${s}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({trigger_id:t,view:i})});c.ok||log.error(`Slack views.open returned non-2xx`,{status:c.status})}async function handleViewSubmission(e,n,r){let i=new Response(null,{status:200});if(e.callbackId!==HITL_FREEFORM_MODAL_CALLBACK_ID)return i;let a;try{a=JSON.parse(e.privateMetadata??``)}catch{return log.warn(`freeform view_submission carries invalid private_metadata`),i}if(!a.continuationToken||!a.requestId||!a.messageTs||!a.channelId||!a.threadTs)return i;let o=e.values?.find(e=>e.blockId===HITL_FREEFORM_MODAL_BLOCK_ID&&e.actionId===HITL_FREEFORM_MODAL_ACTION_ID)?.value??``;if(o.length===0)return i;let s=e.user,d=e.userId,f=s?.teamId??e.teamId??null,p={type:`view_submission`,inputResponses:[parseInputResponse({requestId:a.requestId,text:o})],messageTs:a.messageTs,user:{id:d,username:s?.username,name:s?.name}};return n.waitUntil(dispatchViewInputResponse({ctx:n,deps:r,metadata:a,submission:p,teamId:f,text:o})),i}async function dispatchViewInputResponse(e){let t=await authorizeInputResponse({channelId:e.metadata.channelId,deps:e.deps,submission:e.submission,teamId:e.teamId,threadTs:e.metadata.threadTs});if(t!==null){try{await e.ctx.from(e.metadata.continuationToken).respond(e.submission.inputResponses,{auth:t.auth})}catch(e){log.error(`freeform answer delivery failed`,{error:e});return}try{await updateAnsweredFreeformCard({channelId:e.metadata.channelId,messageTs:e.metadata.messageTs,answerLabel:e.text,userId:e.submission.user.id,deps:e.deps})}catch(e){log.error(`freeform answered-card update failed`,{error:e})}}}async function updateAnsweredHitlCard(e,t){let n=e.actions.find(e=>isHitlAction(e.actionId));if(!n||!n.messageTs)return;let r=n.label??n.selectedOptionValue??n.value;if(!r)return;let i=buildAnsweredHitlMessageBlocks({actionId:n.actionId,answerLabel:r,messageBlocks:e.messageBlocks,userId:n.user.id}),o=await resolveSlackBotToken(t.config.credentials?.botToken),s=await fetch(`https://slack.com/api/chat.update`,{method:`POST`,headers:{authorization:`Bearer ${o}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({channel:e.channelId,ts:n.messageTs,blocks:i,text:`Answered: ${r}`})});if(!s.ok)throw Error(`Slack chat.update returned HTTP ${s.status}`)}async function updateAnsweredFreeformCard(e){let t=buildAnsweredBlocks({promptBlocks:[],answerLabel:e.answerLabel,userId:e.userId}),n=await resolveSlackBotToken(e.deps.config.credentials?.botToken),r=await fetch(`https://slack.com/api/chat.update`,{method:`POST`,headers:{authorization:`Bearer ${n}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({channel:e.channelId,ts:e.messageTs,blocks:t,text:`Answered: ${e.answerLabel}`})});if(!r.ok)throw Error(`Slack chat.update returned HTTP ${r.status}`)}export{handleInteractionPost,parseBlockActionsPayload};
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{parseInputResponse}from"#runtime/input/types.js";import{readSlackTextObject}from"#public/channels/slack/inbound-content.js";import{buildSlackBinding,resolveSlackBotToken,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{HITL_FREEFORM_MODAL_ACTION_ID,HITL_FREEFORM_MODAL_BLOCK_ID,HITL_FREEFORM_MODAL_CALLBACK_ID,buildFreeformModalView,deriveHitlResponse,freeformRequestIdFromActionId,isFreeformAction,isHitlAction}from"#public/channels/slack/hitl.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{updateAnsweredFreeformCard,updateAnsweredHitlCard}from"#public/channels/slack/interaction-cards.js";import{approvalResponderStatePatch,authorizeInputResponse}from"#public/channels/slack/input-response.js";import{bindSlackSessionOperations}from"#public/channels/slack/session-operations.js";const log=createLogger(`slack.interactions`);function parseBlockActionsPayload(e){if(isSharedBlockActionsPayload(e))return parseSharedBlockActionsPayload(e);let t=e,n=t.actions;if(!Array.isArray(n))return null;let r=t.channel?.id,i=t.message,a=i?.thread_ts??i?.ts;if(!r||!a)return null;let o=t.team,s=t.user,c=s.team_id??o?.id,l={id:s.id,username:s.username,name:s.name},u=i?.blocks??[];return{actions:n.map(e=>({actionId:String(e.action_id??``),value:e.value==null?void 0:String(e.value),blockId:e.block_id==null?void 0:String(e.block_id),selectedOptionValue:extractSelectedOptionValue(e),messageTs:i?.ts,label:extractActionLabel(e),user:l})),channelId:r,installationTeamId:readInstallationTeamId(t),threadTs:a,teamId:c,messageBlocks:u}}function isSharedBlockActionsPayload(e){return e.kind===`block_actions`&&Array.isArray(e.actions)}function parseSharedBlockActionsPayload(e){return!e.channelId||!e.threadTs?null:{actions:e.actions.map(t=>({actionId:t.actionId,value:t.value,blockId:t.blockId,selectedOptionValue:t.selectedOptionValue,messageTs:e.messageTs,label:t.label,user:{id:t.user?.id??e.userId,username:t.user?.username,name:t.user?.name}})),channelId:e.channelId,installationTeamId:readInstallationTeamId(e.raw),threadTs:e.threadTs,teamId:e.user?.teamId??e.teamId,messageBlocks:e.messageBlocks??[]}}function extractSelectedOptionValue(e){let t=e.selected_option;return typeof t?.value==`string`?t.value:void 0}function extractActionLabel(e){let t=e.selected_option?.text?.text;if(typeof t==`string`&&t.length>0)return t;let n=e.text?.text;if(typeof n==`string`&&n.length>0)return n}function findPromptBlock(e){return findPromptBlocks(e)[0]}function findPromptBlocks(e){let t=[];for(let n of e){if(typeof n!=`object`||!n)continue;let e=n.type;if(e===`actions`)break;(e===`section`||e===`context`||e===`divider`||e===`image`)&&t.push(n)}return t}function readPromptTextFromBlocks(e){let t=readSlackTextObject(findPromptBlock(e)?.text);return t.length>0?t:void 0}function isObjectRecord(e){return typeof e==`object`&&!!e}function readInstallationTeamId(e){if(!isObjectRecord(e))return;let t=isObjectRecord(e.view)?e.view:void 0,n=isObjectRecord(e.team)?e.team:void 0,r=isObjectRecord(e.user)?e.user:void 0;return[t?.app_installed_team_id,e.app_installed_team_id,n?.id,r?.team_id,t?.team_id].find(e=>typeof e==`string`&&e.length>0)}async function handleInteractionPost(e,t,n){let i=new Response(`ok`,{status:200}),s;try{s=parseSlackWebhookBody(e,{contentType:`application/x-www-form-urlencoded`})}catch{return log.warn(`failed to parse Slack interaction payload`),i}if(s.kind===`view_submission`)return handleViewSubmission(s,t,n);if(s.kind!==`block_actions`)return i;let c=parseBlockActionsPayload(s);if(!c)return i;let l=c.actions.find(e=>isFreeformAction(e.actionId));if(l)return await openFreeformModal({payload:s.raw,interaction:c,freeformAction:l,deps:n}),i;let u=slackContinuationToken(c.channelId,c.threadTs),f=c.actions.flatMap(e=>{let t=deriveHitlResponse(e);return t===null?[]:[{action:e,derived:t}]});if(f.length>0){let e=f[0].action.user;t.waitUntil(dispatchBlockInputResponses({ctx:t,deps:n,interaction:c,submission:{type:`block_actions`,actions:f.map(({action:e})=>e),inputResponses:f.map(({derived:e})=>e.response),messageTs:f[0].action.messageTs,user:e}}))}let h=n.config.onInteraction;if(h){let e=c.actions.filter(e=>!isHitlAction(e.actionId));if(e.length>0){let i=e[0].user,{thread:a,slack:s}=buildSlackBinding({botToken:n.config.credentials?.botToken,channelId:c.channelId,threadTs:c.threadTs,installationTeamId:c.installationTeamId,teamId:c.teamId}),l={...bindSlackSessionOperations({address:u,defaultAuth:buildSlackAuthContext({channelId:c.channelId,teamId:c.teamId,threadTs:c.threadTs,userId:i.id,userName:i.username??i.name}),from:t.from,resolveSession:t.resolveSession,state:{channelId:c.channelId,installationTeamId:c.installationTeamId??null,teamId:c.teamId??null,threadTs:c.threadTs,triggeringUserId:i.id}}),thread:a,slack:s};for(let n of e)t.waitUntil(Promise.resolve(h(n,l)).catch(e=>{log.error(`custom interaction handler failed`,{error:e})}))}}return i}async function dispatchBlockInputResponses(e){let t=await authorizeInputResponse({channelId:e.interaction.channelId,deps:e.deps,installationTeamId:e.interaction.installationTeamId,submission:e.submission,teamId:e.interaction.teamId,threadTs:e.interaction.threadTs});if(t!==null){try{await e.ctx.from(slackContinuationToken(e.interaction.channelId,e.interaction.threadTs)).respond(e.submission.inputResponses,{auth:t.auth,state:approvalResponderStatePatch(e.submission,t.auth)})}catch(e){log.error(`HITL interaction delivery failed`,{error:e});return}if(!e.submission.actions.some(e=>deriveHitlResponse(e)?.kind===`tool-approval`))try{await updateAnsweredHitlCard(e.interaction,e.deps)}catch(e){log.error(`HITL answered-card update failed`,{error:e})}}}async function openFreeformModal(e){let t=e.payload.trigger_id;if(typeof t!=`string`||t.length===0){log.warn(`freeform button click missing trigger_id — cannot open modal`);return}let n=freeformRequestIdFromActionId(e.freeformAction.actionId)??e.freeformAction.value;if(!n){log.warn(`freeform button click missing requestId`);return}let r=e.freeformAction.messageTs;if(!r){log.warn(`freeform button click missing messageTs`);return}let o=buildFreeformModalView({metadata:{continuationToken:slackContinuationToken(e.interaction.channelId,e.interaction.threadTs),channelId:e.interaction.channelId,installationTeamId:e.interaction.installationTeamId,threadTs:e.interaction.threadTs,messageTs:r,requestId:n},prompt:readPromptTextFromBlocks(e.interaction.messageBlocks)}),s=await resolveSlackBotToken(e.deps.config.credentials?.botToken,{teamId:e.interaction.installationTeamId}),c=await fetch(`https://slack.com/api/views.open`,{method:`POST`,headers:{authorization:`Bearer ${s}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({trigger_id:t,view:o})});c.ok||log.error(`Slack views.open returned non-2xx`,{status:c.status})}async function handleViewSubmission(e,n,r){let i=new Response(null,{status:200});if(e.callbackId!==HITL_FREEFORM_MODAL_CALLBACK_ID)return i;let a;try{a=JSON.parse(e.privateMetadata??``)}catch{return log.warn(`freeform view_submission carries invalid private_metadata`),i}if(!a.continuationToken||!a.requestId||!a.messageTs||!a.channelId||!a.threadTs)return i;let o=e.values?.find(e=>e.blockId===HITL_FREEFORM_MODAL_BLOCK_ID&&e.actionId===HITL_FREEFORM_MODAL_ACTION_ID)?.value??``;if(o.length===0)return i;let u=e.user,d=e.userId,f=u?.teamId??e.teamId??null,p=a.installationTeamId??readInstallationTeamId(e.raw),m={type:`view_submission`,inputResponses:[parseInputResponse({requestId:a.requestId,text:o})],messageTs:a.messageTs,user:{id:d,username:u?.username,name:u?.name}};return n.waitUntil(dispatchViewInputResponse({ctx:n,deps:r,installationTeamId:p,metadata:a,submission:m,teamId:f,text:o})),i}async function dispatchViewInputResponse(e){let t=await authorizeInputResponse({channelId:e.metadata.channelId,deps:e.deps,installationTeamId:e.installationTeamId,submission:e.submission,teamId:e.teamId,threadTs:e.metadata.threadTs});if(t!==null){try{await e.ctx.from(e.metadata.continuationToken).respond(e.submission.inputResponses,{auth:t.auth})}catch(e){log.error(`freeform answer delivery failed`,{error:e});return}try{await updateAnsweredFreeformCard({channelId:e.metadata.channelId,messageTs:e.metadata.messageTs,answerLabel:e.text,userId:e.submission.user.id,installationTeamId:e.installationTeamId,deps:e.deps})}catch(e){log.error(`freeform answered-card update failed`,{error:e})}}}export{handleInteractionPost,parseBlockActionsPayload};
|
|
@@ -105,6 +105,8 @@ export interface SlackChannelState {
|
|
|
105
105
|
threadTs: string | null;
|
|
106
106
|
/** Slack team id, when the inbound event carried one. */
|
|
107
107
|
teamId: string | null;
|
|
108
|
+
/** Slack workspace whose app installation supplies this session's credentials. */
|
|
109
|
+
installationTeamId?: string | null;
|
|
108
110
|
/**
|
|
109
111
|
* Slack user id of the actor that triggered the current session/turn.
|
|
110
112
|
* Captured on every inbound mention so default handlers (e.g.
|
|
@@ -160,7 +162,8 @@ export interface SlackInstrumentationMetadata extends Record<string, unknown> {
|
|
|
160
162
|
*/
|
|
161
163
|
export interface SlackChannelCredentials {
|
|
162
164
|
/**
|
|
163
|
-
* Bot token for all outbound Slack Web API calls.
|
|
165
|
+
* Bot token for all outbound Slack Web API calls. Function providers receive
|
|
166
|
+
* the app installation workspace id when Slack supplies one. Falls back to
|
|
164
167
|
* `process.env.SLACK_BOT_TOKEN` when omitted.
|
|
165
168
|
*/
|
|
166
169
|
readonly botToken?: SlackBotToken;
|
|
@@ -181,6 +184,8 @@ export interface SlackChannelCredentials {
|
|
|
181
184
|
export interface SlackReceiveTarget {
|
|
182
185
|
readonly channelId: string;
|
|
183
186
|
readonly threadTs?: string;
|
|
187
|
+
/** Slack workspace whose app installation supplies credentials for this send. */
|
|
188
|
+
readonly installationTeamId?: string;
|
|
184
189
|
/**
|
|
185
190
|
* Optional message posted into the Slack channel before the agent runs.
|
|
186
191
|
* The post becomes the thread root and the first turn is threaded under
|