eve 0.17.0 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/src/channel/types.d.ts +11 -0
  3. package/dist/src/chunks/{use-eve-agent-BEOUv37s.js → use-eve-agent-Bh1NmXO9.js} +8 -5
  4. package/dist/src/chunks/{use-eve-agent-C25KOe9i.js → use-eve-agent-Dvy_Wjvq.js} +8 -5
  5. package/dist/src/cli/dev/tui/status-line.d.ts +1 -1
  6. package/dist/src/cli/dev/tui/tui.d.ts +2 -0
  7. package/dist/src/cli/dev/tui/tui.js +1 -1
  8. package/dist/src/cli/dev/url-target.d.ts +14 -0
  9. package/dist/src/cli/dev/url-target.js +1 -0
  10. package/dist/src/cli/run.js +3 -3
  11. package/dist/src/client/client.js +1 -1
  12. package/dist/src/compiled/.vendor-stamp.json +4 -4
  13. package/dist/src/compiled/@chat-adapter/slack/api.d.ts +132 -0
  14. package/dist/src/compiled/@chat-adapter/slack/api.js +1 -0
  15. package/dist/src/compiled/@chat-adapter/slack/blocks.js +1 -1
  16. package/dist/src/compiled/@chat-adapter/slack/format.d.ts +33 -0
  17. package/dist/src/compiled/@chat-adapter/slack/format.js +1 -0
  18. package/dist/src/compiled/@chat-adapter/slack/index.js +28 -28
  19. package/dist/src/compiled/@chat-adapter/slack/types-AntsVifS.d.ts +187 -0
  20. package/dist/src/compiled/@chat-adapter/slack/webhook.d.ts +10 -0
  21. package/dist/src/compiled/@chat-adapter/slack/webhook.js +1 -0
  22. package/dist/src/compiled/@workflow/core/classify-error.d.ts +15 -0
  23. package/dist/src/compiled/@workflow/core/index.js +2 -2
  24. package/dist/src/compiled/@workflow/core/runtime.js +27 -27
  25. package/dist/src/compiled/@workflow/core/serialization.d.ts +12 -3
  26. package/dist/src/compiled/@workflow/core/step/context-storage.d.ts +10 -0
  27. package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
  28. package/dist/src/compiled/@workflow/core/workflow.js +1 -1
  29. package/dist/src/compiled/_chunks/node/chunk-AYN7QRWH-B__hKQV7.js +1 -0
  30. package/dist/src/compiled/_chunks/node/chunk-HFKBBKCJ-CWaVIQuL.js +1 -0
  31. package/dist/src/compiled/_chunks/workflow/{attribute-changes-DUxG-Gic.js → attribute-changes-zAifvEhb.js} +1 -1
  32. package/dist/src/compiled/_chunks/workflow/{resume-hook-CUCPW67D.js → resume-hook-C15uJ2ik.js} +1 -1
  33. package/dist/src/compiled/_chunks/workflow/run-BJUPZ0Ni.js +1 -0
  34. package/dist/src/compiled/_chunks/workflow/{sleep-Dxuzj5to.js → sleep-C8B-TYir.js} +1 -1
  35. package/dist/src/compiler/manifest.d.ts +2 -2
  36. package/dist/src/compiler/manifest.js +1 -1
  37. package/dist/src/compiler/normalize-agent-config.js +1 -1
  38. package/dist/src/context/keys.d.ts +2 -0
  39. package/dist/src/context/keys.js +1 -1
  40. package/dist/src/evals/assertions/run.js +2 -2
  41. package/dist/src/execution/create-session-step.d.ts +2 -0
  42. package/dist/src/execution/create-session-step.js +1 -1
  43. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  44. package/dist/src/execution/durable-session-store.d.ts +3 -0
  45. package/dist/src/execution/remote-agent-dispatch.js +1 -1
  46. package/dist/src/execution/runtime-context.js +1 -1
  47. package/dist/src/execution/session.d.ts +6 -1
  48. package/dist/src/execution/session.js +3 -3
  49. package/dist/src/execution/subagent-invocation.d.ts +20 -5
  50. package/dist/src/execution/subagent-invocation.js +2 -2
  51. package/dist/src/execution/subagent-tool.d.ts +7 -0
  52. package/dist/src/execution/subagent-tool.js +1 -1
  53. package/dist/src/execution/workflow-entry.js +1 -1
  54. package/dist/src/harness/advertised-tools.d.ts +33 -0
  55. package/dist/src/harness/advertised-tools.js +1 -0
  56. package/dist/src/harness/subagent-depth.d.ts +17 -0
  57. package/dist/src/harness/subagent-depth.js +1 -0
  58. package/dist/src/harness/tool-loop.js +1 -1
  59. package/dist/src/harness/turn-tag-state.d.ts +30 -22
  60. package/dist/src/harness/turn-tag-state.js +1 -1
  61. package/dist/src/harness/types.d.ts +28 -0
  62. package/dist/src/harness/workflow-continuation-security.d.ts +1 -0
  63. package/dist/src/harness/workflow-continuation-security.js +1 -1
  64. package/dist/src/internal/application/package.js +1 -1
  65. package/dist/src/internal/authored-definition/core.js +1 -1
  66. package/dist/src/internal/http/basic-auth.d.ts +1 -0
  67. package/dist/src/internal/http/basic-auth.js +1 -0
  68. package/dist/src/internal/nitro/host/configure-nitro-routes.js +2 -2
  69. package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
  70. package/dist/src/internal/nitro/routes/channel-route-context.d.ts +5 -0
  71. package/dist/src/internal/nitro/routes/channel-route-context.js +1 -0
  72. package/dist/src/internal/nitro/routes/info.d.ts +2 -6
  73. package/dist/src/internal/nitro/routes/info.js +1 -1
  74. package/dist/src/protocol/routes.d.ts +2 -4
  75. package/dist/src/public/channels/eve.d.ts +5 -4
  76. package/dist/src/public/channels/eve.js +1 -1
  77. package/dist/src/public/channels/slack/api.d.ts +3 -6
  78. package/dist/src/public/channels/slack/api.js +1 -1
  79. package/dist/src/public/channels/slack/inbound.d.ts +11 -9
  80. package/dist/src/public/channels/slack/inbound.js +1 -1
  81. package/dist/src/public/channels/slack/interactions.d.ts +2 -1
  82. package/dist/src/public/channels/slack/interactions.js +1 -1
  83. package/dist/src/public/channels/slack/mrkdwn.d.ts +3 -35
  84. package/dist/src/public/channels/slack/mrkdwn.js +1 -1
  85. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  86. package/dist/src/public/channels/slack/verify.d.ts +4 -42
  87. package/dist/src/public/channels/slack/verify.js +1 -1
  88. package/dist/src/public/definitions/agent.d.ts +1 -1
  89. package/dist/src/public/index.d.ts +1 -1
  90. package/dist/src/runtime/resolve-agent.js +1 -1
  91. package/dist/src/services/dev-client/client-options.d.ts +5 -1
  92. package/dist/src/services/dev-client/client-options.js +1 -1
  93. package/dist/src/setup/boxes/preflight.js +1 -1
  94. package/dist/src/setup/boxes/select-model.js +1 -1
  95. package/dist/src/setup/flows/model.js +1 -1
  96. package/dist/src/setup/scaffold/create/project.d.ts +1 -1
  97. package/dist/src/setup/scaffold/create/project.js +1 -1
  98. package/dist/src/setup/scaffold/create/web-template.d.ts +3 -0
  99. package/dist/src/setup/scaffold/create/web-template.js +1 -1
  100. package/dist/src/shared/agent-definition.d.ts +37 -0
  101. package/dist/src/shared/default-agent-model.d.ts +1 -1
  102. package/dist/src/shared/default-agent-model.js +1 -1
  103. package/dist/src/svelte/index.js +1 -1
  104. package/dist/src/svelte/use-eve-agent.js +1 -1
  105. package/dist/src/vue/index.js +1 -1
  106. package/dist/src/vue/use-eve-agent.js +1 -1
  107. package/docs/agent-config.md +35 -8
  108. package/docs/channels/eve.mdx +3 -2
  109. package/docs/concepts/sessions-runs-and-streaming.md +2 -2
  110. package/docs/getting-started.mdx +5 -5
  111. package/docs/guides/dev-tui.md +7 -1
  112. package/docs/reference/cli.md +10 -1
  113. package/docs/reference/typescript-api.md +1 -1
  114. package/docs/subagents.mdx +19 -0
  115. package/package.json +5 -5
  116. package/dist/src/compiled/_chunks/node/chunk-AYN7QRWH-Csu8sG38.js +0 -1
  117. package/dist/src/compiled/_chunks/workflow/run-CVlF84yI.js +0 -1
@@ -1,14 +1,10 @@
1
1
  import type { NitroArtifactsConfig } from "#internal/nitro/routes/runtime-artifacts.js";
2
2
  type AgentInfoRouteMode = "development" | "production";
3
- interface AgentInfoRouteInput extends NitroArtifactsConfig {
3
+ export interface AgentInfoRouteInput extends NitroArtifactsConfig {
4
4
  readonly mode?: AgentInfoRouteMode;
5
5
  }
6
6
  /**
7
7
  * Builds the package-owned JSON inspection response for the current agent.
8
- *
9
- * The route keeps the same default auth chain as the eve channel:
10
- * local development requests are accepted by hostname, while deployed
11
- * Vercel targets require a valid OIDC bearer.
12
8
  */
13
- export declare function handleAgentInfoRequest(input: AgentInfoRouteInput, request: Request): Promise<Response>;
9
+ export declare function handleAgentInfoRequest(input: AgentInfoRouteInput): Promise<Response>;
14
10
  export {};
@@ -1 +1 @@
1
- import{getVercelOidcToken}from"#compiled/@vercel/oidc/index.js";import{buildAgentInfoResponseFromManifest}from"#internal/nitro/routes/agent-info/build-agent-info-response-from-manifest.js";import{loadAgentInfoManifestData,resolveAgentInfoCompiledArtifactsSource}from"#internal/nitro/routes/agent-info/load-agent-info-data.js";import{localDev,routeAuth,vercelOidc}from"#public/channels/auth.js";async function createAgentInfoPayload(e){let r=await loadAgentInfoManifestData({compiledArtifactsSource:resolveAgentInfoCompiledArtifactsSource(e)});return buildAgentInfoResponseFromManifest(r,{mode:e.mode??`development`,gatewayCredentials:await resolveGatewayCredentialPresence(r.manifest.config.model.routing)})}function hasEnvValue(e){return e!==void 0&&e.trim()!==``}async function resolveGatewayCredentialPresence(t){let n=hasEnvValue(process.env.AI_GATEWAY_API_KEY);if(t.kind===`external`||n)return{apiKey:n,oidc:!1};try{return await getVercelOidcToken(),{apiKey:!1,oidc:!0}}catch{return{apiKey:!1,oidc:!1}}}async function handleAgentInfoRequest(e,t){let n=await routeAuth(t,[vercelOidc(),localDev()]);return n instanceof Response?n:new Response(JSON.stringify(await createAgentInfoPayload(e)),{headers:{"cache-control":`no-store`,"content-type":`application/json; charset=utf-8`}})}export{handleAgentInfoRequest};
1
+ import{getVercelOidcToken}from"#compiled/@vercel/oidc/index.js";import{buildAgentInfoResponseFromManifest}from"#internal/nitro/routes/agent-info/build-agent-info-response-from-manifest.js";import{loadAgentInfoManifestData,resolveAgentInfoCompiledArtifactsSource}from"#internal/nitro/routes/agent-info/load-agent-info-data.js";async function createAgentInfoPayload(e){let r=await loadAgentInfoManifestData({compiledArtifactsSource:resolveAgentInfoCompiledArtifactsSource(e)});return buildAgentInfoResponseFromManifest(r,{mode:e.mode??(e.dev===!1?`production`:`development`),gatewayCredentials:await resolveGatewayCredentialPresence(r.manifest.config.model.routing)})}function hasEnvValue(e){return e!==void 0&&e.trim()!==``}async function resolveGatewayCredentialPresence(t){let n=hasEnvValue(process.env.AI_GATEWAY_API_KEY);if(t.kind===`external`||n)return{apiKey:n,oidc:!1};try{return await getVercelOidcToken(),{apiKey:!1,oidc:!0}}catch{return{apiKey:!1,oidc:!1}}}async function handleAgentInfoRequest(e){return new Response(JSON.stringify(await createAgentInfoPayload(e)),{headers:{"cache-control":`no-store`,"content-type":`application/json; charset=utf-8`}})}export{handleAgentInfoRequest};
@@ -9,10 +9,8 @@ export declare const EVE_ROUTE_PREFIX = "/eve/v1";
9
9
  export declare const EVE_HEALTH_ROUTE_PATH = "/eve/v1/health";
10
10
  /**
11
11
  * Stable framework-owned route exposing the JSON inspection payload for
12
- * the current agent. Nitro registers this route with the application
13
- * surface, and the handler uses the same default auth chain as the eve
14
- * channel: local development accepts loopback requests, while deployed
15
- * Vercel targets require OIDC.
12
+ * the current agent. The eve channel registers and authenticates this route
13
+ * with the same `auth` input as its session routes.
16
14
  */
17
15
  export declare const EVE_INFO_ROUTE_PATH = "/eve/v1/info";
18
16
  /**
@@ -104,9 +104,10 @@ export interface EveChannel extends Channel {
104
104
  }
105
105
  /**
106
106
  * Builds the default eve HTTP channel: a {@link defineChannel} instance serving the
107
- * built-in `/eve/v1` routes (POST creates a session, POST delivers a follow-up, GET
108
- * streams a session's NDJSON event feed). Every route runs {@link EveChannelInput.auth}
109
- * via {@link routeAuth} before dispatching. Default-export the result as your
110
- * `agent/channels/eve.ts` channel; reach for {@link defineChannel} directly only for a custom transport.
107
+ * built-in `/eve/v1` routes (GET inspects the agent, POST creates a session, POST
108
+ * delivers a follow-up, GET streams a session's NDJSON event feed). Every route
109
+ * runs {@link EveChannelInput.auth} via {@link routeAuth} before dispatching.
110
+ * Default-export the result as your `agent/channels/eve.ts` channel; reach for
111
+ * {@link defineChannel} directly only for a custom transport.
111
112
  */
112
113
  export declare function eveChannel(input: EveChannelInput): EveChannel;
@@ -1,2 +1,2 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{hasInternalRefScheme}from"#internal/attachments/url-refs.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_VERSION_HEADER}from"#protocol/message.js";import{isInputResponse}from"#runtime/input/types.js";import{parseJsonObject}from"#shared/json.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{routeAuth}from"#public/channels/auth.js";import{GET,POST,defineChannel}from"#public/definitions/defineChannel.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 t=mergeUploadPolicy(e.uploadPolicy);return defineChannel({cors:normalizeEveCors(e.cors),routes:[POST(`/eve/v1/session`,async(n,{send:r})=>{let i=await routeAuth(n,e.auth);if(i instanceof Response)return i;let a=i,o;try{o=await n.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof o!=`object`||!o)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let s=parseCreateBody(o);if(s instanceof Response)return s;let c=checkUploadPolicy(s,t);if(c!==null)return c;let l=await resolveOnMessage({auth:a,config:e,message:s.message,request:n});if(l instanceof Response)return l;if(!l.dispatch)return droppedMessageResponse();let u=`eve:${crypto.randomUUID()}`,d=await r(createSendPayload(s,mergeContext(s.context,l.context)),{auth:l.auth,callback:s.callback,continuationToken:u,mode:s.mode});return Response.json({continuationToken:d.continuationToken,ok:!0,sessionId:d.id},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:d.id},status:202})}),POST(`/eve/v1/session/:sessionId`,async(n,{send:r,getSession:i,params:a})=>{let o=await routeAuth(n,e.auth);if(o instanceof Response)return o;let s=o,c=a.sessionId;if(!c)return Response.json({error:`Missing session id.`,ok:!1},{status:400});try{i(c)}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}let l;try{l=await n.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof l!=`object`||!l)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let u=parseContinueBody(l);if(u instanceof Response)return u;let d=checkUploadPolicy(u,t);if(d!==null)return d;let f=u.context,p=s;if(u.message!==void 0){let t=await resolveOnMessage({auth:s,config:e,message:u.message,request:n,sessionId:c});if(t instanceof Response)return t;if(!t.dispatch)return droppedMessageResponse();f=mergeContext(u.context,t.context),p=t.auth}let m=await r({inputResponses:u.inputResponses,message:u.message,context:f,outputSchema:u.outputSchema},{auth:p,continuationToken:u.continuationToken});return Response.json({ok:!0,sessionId:m.id},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:m.id},status:200})}),GET(`/eve/v1/session/:sessionId/stream`,async(t,{getSession:n,params:c})=>{let l=await routeAuth(t,e.auth);if(l instanceof Response)return l;let u=c.sessionId;if(!u)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let d=parseStartIndex(t);if(d instanceof Response)return d;try{let e=serializeAsNdjson(await n(u).getEventStream({startIndex:d}));return new Response(e,{headers:{"cache-control":`no-store, no-transform`,"content-type":EVE_MESSAGE_STREAM_CONTENT_TYPE,"x-accel-buffering":`no`,[EVE_SESSION_ID_HEADER]:u,[EVE_STREAM_FORMAT_HEADER]:EVE_MESSAGE_STREAM_FORMAT,[EVE_STREAM_VERSION_HEADER]:EVE_MESSAGE_STREAM_VERSION}})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}})],events:e.events})}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{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)}catch(n){let r=logError(log,`onMessage handler failed`,n,{sessionId:e.sessionId});return Response.json({error:`onMessage handler failed.`,errorId:r,ok:!1},{status:500})}return r==null?{dispatch:!1}:r.context===void 0?{auth:r.auth,dispatch:!0}:{auth:r.auth,context:r.context,dispatch:!0}}function defaultOnMessage(e){return{auth:defaultEveAuth(e)}}function droppedMessageResponse(){return new Response(null,{headers:{"cache-control":`no-store`},status:204})}function parseCreateBody(e){let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseModeField(e.mode);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:t===void 0?Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400}):{callback:r,message:t,mode:i,context:n,outputSchema:a}}function parseContinueBody(e){let t=typeof e.continuationToken==`string`&&e.continuationToken.length>0?e.continuationToken:void 0;if(t===void 0)return Response.json({error:`Missing or empty 'continuationToken' field.`,ok:!1},{status:400});let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseInputResponses(e.inputResponses);if(r instanceof Response)return r;let i=parseClientContextField(e.clientContext);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:n===void 0&&r===void 0?Response.json({error:`Expected a non-empty 'message', a non-empty 'inputResponses' array, or both.`,ok:!1},{status:400}):{message:n,continuationToken:t,inputResponses:r,context:i,outputSchema:a}}function createSendPayload(e,t=e.context){if(t===void 0&&e.outputSchema===void 0)return e.message;let n={message:e.message};return t!==void 0&&(n.context=t),e.outputSchema!==void 0&&(n.outputSchema=e.outputSchema),n}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});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(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:
1
+ import{createLogger,logError}from"#internal/logging.js";import{hasInternalRefScheme}from"#internal/attachments/url-refs.js";import{EVE_INFO_ROUTE_PATH}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_VERSION_HEADER}from"#protocol/message.js";import{isInputResponse}from"#runtime/input/types.js";import{parseJsonObject}from"#shared/json.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{readAgentInfoRouteResponse}from"#internal/nitro/routes/channel-route-context.js";import{GET,POST,defineChannel}from"#public/definitions/defineChannel.js";import{routeAuth}from"#public/channels/auth.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 t=mergeUploadPolicy(e.uploadPolicy);return defineChannel({cors:normalizeEveCors(e.cors),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/v1/session`,async(n,{send:r})=>{let i=await routeAuth(n,e.auth);if(i instanceof Response)return i;let a=i,o;try{o=await n.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof o!=`object`||!o)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let s=parseCreateBody(o);if(s instanceof Response)return s;let c=checkUploadPolicy(s,t);if(c!==null)return c;let l=await resolveOnMessage({auth:a,config:e,message:s.message,request:n});if(l instanceof Response)return l;if(!l.dispatch)return droppedMessageResponse();let u=`eve:${crypto.randomUUID()}`,d=await r(createSendPayload(s,mergeContext(s.context,l.context)),{auth:l.auth,callback:s.callback,continuationToken:u,mode:s.mode});return Response.json({continuationToken:d.continuationToken,ok:!0,sessionId:d.id},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:d.id},status:202})}),POST(`/eve/v1/session/:sessionId`,async(n,{send:r,getSession:i,params:a})=>{let o=await routeAuth(n,e.auth);if(o instanceof Response)return o;let s=o,c=a.sessionId;if(!c)return Response.json({error:`Missing session id.`,ok:!1},{status:400});try{i(c)}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}let l;try{l=await n.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof l!=`object`||!l)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let u=parseContinueBody(l);if(u instanceof Response)return u;let d=checkUploadPolicy(u,t);if(d!==null)return d;let f=u.context,p=s;if(u.message!==void 0){let t=await resolveOnMessage({auth:s,config:e,message:u.message,request:n,sessionId:c});if(t instanceof Response)return t;if(!t.dispatch)return droppedMessageResponse();f=mergeContext(u.context,t.context),p=t.auth}let m=await r({inputResponses:u.inputResponses,message:u.message,context:f,outputSchema:u.outputSchema},{auth:p,continuationToken:u.continuationToken});return Response.json({ok:!0,sessionId:m.id},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:m.id},status:200})}),GET(`/eve/v1/session/:sessionId/stream`,async(t,{getSession:n,params:r})=>{let l=await routeAuth(t,e.auth);if(l instanceof Response)return l;let u=r.sessionId;if(!u)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let d=parseStartIndex(t);if(d instanceof Response)return d;try{let e=serializeAsNdjson(await n(u).getEventStream({startIndex:d}));return new Response(e,{headers:{"cache-control":`no-store, no-transform`,"content-type":EVE_MESSAGE_STREAM_CONTENT_TYPE,"x-accel-buffering":`no`,[EVE_SESSION_ID_HEADER]:u,[EVE_STREAM_FORMAT_HEADER]:EVE_MESSAGE_STREAM_FORMAT,[EVE_STREAM_VERSION_HEADER]:EVE_MESSAGE_STREAM_VERSION}})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}})],events:e.events})}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{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)}catch(n){let r=logError(log,`onMessage handler failed`,n,{sessionId:e.sessionId});return Response.json({error:`onMessage handler failed.`,errorId:r,ok:!1},{status:500})}return r==null?{dispatch:!1}:r.context===void 0?{auth:r.auth,dispatch:!0}:{auth:r.auth,context:r.context,dispatch:!0}}function defaultOnMessage(e){return{auth:defaultEveAuth(e)}}function droppedMessageResponse(){return new Response(null,{headers:{"cache-control":`no-store`},status:204})}function parseCreateBody(e){let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseModeField(e.mode);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:t===void 0?Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400}):{callback:r,message:t,mode:i,context:n,outputSchema:a}}function parseContinueBody(e){let t=typeof e.continuationToken==`string`&&e.continuationToken.length>0?e.continuationToken:void 0;if(t===void 0)return Response.json({error:`Missing or empty 'continuationToken' field.`,ok:!1},{status:400});let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseInputResponses(e.inputResponses);if(r instanceof Response)return r;let i=parseClientContextField(e.clientContext);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:n===void 0&&r===void 0?Response.json({error:`Expected a non-empty 'message', a non-empty 'inputResponses' array, or both.`,ok:!1},{status:400}):{message:n,continuationToken:t,inputResponses:r,context:i,outputSchema:a}}function createSendPayload(e,t=e.context){if(t===void 0&&e.outputSchema===void 0)return e.message;let n={message:e.message};return t!==void 0&&(n.context=t),e.outputSchema!==void 0&&(n.outputSchema=e.outputSchema),n}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});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(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
2
  ${e}`}function parseStartIndex(e){let t=new URL(e.url).searchParams.get(`startIndex`);if(t===null)return;let n=Number.parseInt(t,10);return!Number.isSafeInteger(n)||n<0?Response.json({error:`Expected startIndex to be a non-negative integer.`,ok:!1},{status:400}):n}function serializeAsNdjson(e){let t=new TextEncoder;return e.pipeThrough(new TransformStream({transform(e,n){n.enqueue(t.encode(`${JSON.stringify(e)}\n`))}}))}export{defaultEveAuth,eveChannel};
@@ -14,13 +14,14 @@
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, type SlackBotToken as SlackPrimitiveBotToken } from "#compiled/@chat-adapter/slack/api.js";
17
18
  import { type CardElement, type FileUpload } from "#compiled/chat/index.js";
18
19
  /**
19
20
  * Slack bot token, materialized either as a literal `xoxb-...` string or
20
21
  * as a (possibly async) function that returns one. The function form
21
22
  * supports secret-manager lookups and credential rotation.
22
23
  */
23
- export type SlackBotToken = string | (() => string | Promise<string>);
24
+ export type SlackBotToken = SlackPrimitiveBotToken;
24
25
  /**
25
26
  * Builds the channel-local continuation token (`<channelId>:<threadTs>`).
26
27
  * The runtime's `send()` later namespaces it with the channel's
@@ -39,11 +40,7 @@ export declare function resolveSlackBotToken(token?: SlackBotToken): Promise<str
39
40
  * carries Slack's error code on failure, and method-specific fields pass
40
41
  * through verbatim. Callers inspect `ok` themselves.
41
42
  */
42
- export interface SlackApiResponse {
43
- readonly ok: boolean;
44
- readonly error?: string;
45
- readonly [key: string]: unknown;
46
- }
43
+ export type SlackApiResponse = SlackPrimitiveApiResponse;
47
44
  /**
48
45
  * Low-level POST to a Slack Web API method, signed with the bot token
49
46
  * and form-encoded. Form is the only safe default: Slack's JSON support
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{isCardElement}from"#compiled/chat/index.js";import{encodeSlackApiBody}from"#public/channels/slack/api-encoding.js";import{cardToBlocks,cardToFallbackText}from"#public/channels/slack/blocks.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{gfmToSlackMrkdwn,rewriteBareMentions,slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";const log=createLogger(`slack.api`);function slackContinuationToken(e,t){return`${e}:${t}`}async function resolveSlackBotToken(e){let t=e??process.env.SLACK_BOT_TOKEN;if(!t)throw Error(`SLACK_BOT_TOKEN is required.`);return typeof t==`function`?await t():t}async function callSlackApi(e){let t=await resolveSlackBotToken(e.botToken),n=encodeSlackApiBody(e.body);return(await fetch(`https://slack.com/api/${e.operation}`,{method:`POST`,headers:{authorization:`Bearer ${t}`,"content-type":n.contentType},body:n.body})).json()}function createSlackRequester(e){return(t,n)=>callSlackApi({botToken:e,operation:t,body:n})}function buildSlackBinding(e){let n=createSlackRequester(e.botToken),r=[],i=e.threadTs;function handleMessageTs(t){i||t===i||(i=t,e.onThreadTsChanged?.(t))}async function uploadFiles(t,n){if(t.length===0)return{fileIds:[],raw:{ok:!0}};let r=n?.channelId??e.channelId,a=n?.threadTs??i,o=await resolveSlackBotToken(e.botToken),s=[];for(let n of t){let t=await readFileBytes(n.data),r=await callSlackApi({botToken:e.botToken,operation:`files.getUploadURLExternal`,body:{filename:n.filename,length:t.byteLength}});if(r.ok!==!0||typeof r.upload_url!=`string`||typeof r.file_id!=`string`)throw Error(`Slack files.getUploadURLExternal failed: ${r.error??`unknown_error`}`);let i=await fetch(r.upload_url,{method:`POST`,headers:{authorization:`Bearer ${o}`,"content-type":`application/octet-stream`},body:t});if(!i.ok)throw Error(`Slack upload POST returned HTTP ${i.status} for ${n.filename}.`);s.push(r.file_id)}let c={files:t.map((e,t)=>({id:s[t],title:e.filename}))};r&&(c.channel_id=r),a&&(c.thread_ts=a),n?.initialComment&&(c.initial_comment=n.initialComment);let l=await callSlackApi({botToken:e.botToken,operation:`files.completeUploadExternal`,body:c});if(l.ok!==!0)throw Error(`Slack files.completeUploadExternal failed: ${l.error??`unknown_error`}`);return{fileIds:s,raw:l}}return{thread:{recentMessages:r,async post(t){let r=normalizePostInput(t),a=r.files??[],o=`blocks`in r||`card`in r;if(a.length>0&&!o){let e=await uploadFiles(a,{initialComment:`markdown`in r?rewriteBareMentions(gfmToSlackMrkdwn(r.markdown)):`text`in r?rewriteBareMentions(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 l=await n(`chat.postMessage`,buildPostMessageBody(r,e.channelId,i));if(l.ok!==!0)throw Error(`Slack chat.postMessage failed: ${l.error??`unknown_error`}`);let u=typeof l.ts==`string`?l.ts:``;if(handleMessageTs(u),a.length>0&&o)try{await uploadFiles(a)}catch(e){log.warn(`file upload after structured post failed`,{error:e})}return{id:u,raw:l}},async postEphemeral(t,r){let a=buildPostMessageBody(normalizePostInput(r),e.channelId,i);a.user=t;let o=await n(`chat.postEphemeral`,a);if(o.ok!==!0)throw Error(`Slack chat.postEphemeral failed: ${o.error??`unknown_error`}`);return{id:typeof o.message_ts==`string`?o.message_ts:``,raw:o}},async postDirectMessage(e,t){let r=await n(`conversations.open`,{users:e}),i=r.ok===!0?r.channel?.id:void 0;if(typeof i!=`string`||i.length===0)throw Error(`Slack conversations.open failed: ${r.error??`unknown_error`}`);let a=await n(`chat.postMessage`,buildPostMessageBody(normalizePostInput(t),i,``));if(a.ok!==!0)throw Error(`Slack chat.postMessage failed: ${a.error??`unknown_error`}`);return{id:typeof a.ts==`string`?a.ts:``,raw:a}},async startTyping(r){if(!(!e.channelId||!i))try{let t=r===void 0?``:truncateTypingStatus(r),a={channel_id:e.channelId,thread_ts:i,status:t};t.length>0&&(a.loading_messages=[t]);let s=await n(`assistant.threads.setStatus`,a);s.ok!==!0&&log.warn(`assistant.threads.setStatus returned not-ok`,{error:s.error})}catch(n){logError(log,`startTyping threw — swallowed`,n,{channelId:e.channelId})}},async refresh(){if(r.length=0,!(!e.channelId||!i))try{let t=await n(`conversations.replies`,{channel:e.channelId,ts:i,limit:50});if(t.ok!==!0||!Array.isArray(t.messages)){log.warn(`conversations.replies returned not-ok`,{error:t.error});return}for(let e of t.messages)r.push(parseThreadMessage(e,i))}catch(n){logError(log,`refresh threw — swallowed`,n,{channelId:e.channelId})}},mentionUser(e){return`<@${e}>`}},slack:{channelId:e.channelId,get threadTs(){return i},teamId:e.teamId,request:n,uploadFiles}}}function normalizePostInput(e){return typeof e==`string`?{markdown:e}:isCardElement(e)?{card:e}:e}function buildPostMessageBody(e,t,n){let r={channel:t,unfurl_links:!1,unfurl_media:!1};return n&&(r.thread_ts=n),`card`in e?(r.blocks=cardToBlocks(e.card),r.text=e.fallbackText??cardToFallbackText(e.card),r):`blocks`in e?(r.blocks=e.blocks,e.text!==void 0&&(r.text=e.text),r):`markdown`in e?(r.markdown_text=rewriteBareMentions(e.markdown),r):(r.text=rewriteBareMentions(e.text),r)}function parseThreadMessage(e,t){let n=typeof e.text==`string`?e.text:``,r=typeof e.ts==`string`?e.ts:``,i=typeof e.thread_ts==`string`?e.thread_ts:t,a=typeof e.user==`string`?e.user:void 0,o=typeof e.bot_id==`string`?e.bot_id:void 0;return{text:n,markdown:slackMrkdwnToGfm(n),user:a,botId:o,ts:r,threadTs:i,isMe:o!==void 0,raw:e}}async function readFileBytes(e){if(e instanceof ArrayBuffer)return Buffer.from(e);if(typeof Blob<`u`&&e instanceof Blob)return Buffer.from(await e.arrayBuffer());if(ArrayBuffer.isView(e)){let t=e;return Buffer.from(t.buffer,t.byteOffset,t.byteLength)}throw Error(`FileUpload.data must be a Buffer, ArrayBuffer, or Blob.`)}export{buildSlackBinding,callSlackApi,resolveSlackBotToken,slackContinuationToken};
1
+ import{createLogger,logError}from"#internal/logging.js";import{callSlackApi as callSlackApi$1,fetchSlackThreadReplies,postSlackEphemeral,postSlackMessage,resolveSlackBotToken as resolveSlackBotToken$1,uploadSlackFiles}from"#compiled/@chat-adapter/slack/api.js";import{isCardElement}from"#compiled/chat/index.js";import{cardToBlocks,cardToFallbackText}from"#public/channels/slack/blocks.js";import{truncateTypingStatus}from"#public/channels/slack/limits.js";import{gfmToSlackMrkdwn,rewriteBareMentions,slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";const log=createLogger(`slack.api`);function slackContinuationToken(e,t){return`${e}:${t}`}async function resolveSlackBotToken(e){let t=e??process.env.SLACK_BOT_TOKEN;if(!t)throw Error(`SLACK_BOT_TOKEN is required.`);return resolveSlackBotToken$1(t)}async function callSlackApi(e){return callSlackApi$1(e.operation,normalizeSlackApiBody(e.body),createSlackApiOptions(e.botToken))}function createSlackRequester(e){return(t,n)=>callSlackApi({botToken:e,operation:t,body:n})}function buildSlackBinding(e){let n=createSlackRequester(e.botToken),o=[],c=e.threadTs;function handleMessageTs(t){c||t===c||(c=t,e.onThreadTsChanged?.(t))}async function uploadFiles(t,n){let r=n?.channelId??e.channelId,i=n?.threadTs??c;return uploadSlackFiles(t.map(toSlackFileUpload),{...createSlackApiOptions(e.botToken),channelId:r||void 0,initialComment:n?.initialComment,threadTs:i||void 0})}return{thread:{recentMessages:o,async post(t){let n=normalizePostInput(t),r=n.files??[],i=`blocks`in n||`card`in n;if(r.length>0&&!i){let e=await uploadFiles(r,{initialComment:`markdown`in n?rewriteBareMentions(gfmToSlackMrkdwn(n.markdown)):`text`in n?rewriteBareMentions(n.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 o=await postSlackMessage(buildPostMessageOptions(n,e.channelId,c,e.botToken)),s=o.id;if(handleMessageTs(s),r.length>0&&i)try{await uploadFiles(r)}catch(e){log.warn(`file upload after structured post failed`,{error:e})}return{id:s,raw:o.raw}},async postEphemeral(t,n){let r=await postSlackEphemeral({...buildPostMessageOptions(normalizePostInput(n),e.channelId,c,e.botToken),user:t});return{id:r.id,raw:r.raw}},async postDirectMessage(t,r){let i=await n(`conversations.open`,{users:t}),o=i.ok===!0?i.channel?.id:void 0;if(typeof o!=`string`||o.length===0)throw Error(`Slack conversations.open failed: ${i.error??`unknown_error`}`);let s=await postSlackMessage(buildPostMessageOptions(normalizePostInput(r),o,``,e.botToken));return{id:s.id,raw:s.raw}},async startTyping(r){if(!(!e.channelId||!c))try{let t=r===void 0?``:truncateTypingStatus(r),i={channel_id:e.channelId,thread_ts:c,status:t};t.length>0&&(i.loading_messages=[t]);let a=await n(`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})}},async refresh(){if(o.length=0,!(!e.channelId||!c))try{let t=await fetchSlackThreadReplies({...createSlackApiOptions(e.botToken),channel:e.channelId,limit:50,ts:c});for(let e of t.messages)o.push(parseThreadMessage(e,c))}catch(n){logError(log,`refresh threw — swallowed`,n,{channelId:e.channelId})}},mentionUser(e){return`<@${e}>`}},slack:{channelId:e.channelId,get threadTs(){return c},teamId:e.teamId,request:n,uploadFiles}}}function normalizePostInput(e){return typeof e==`string`?{markdown:e}:isCardElement(e)?{card:e}:e}function buildPostMessageOptions(e,t,n,r){let i={...createSlackApiOptions(r),channel:t,threadTs:n||void 0,unfurlLinks:!1,unfurlMedia:!1};return`card`in e?(i.blocks=cardToBlocks(e.card),i.text=e.fallbackText??cardToFallbackText(e.card),i):`blocks`in e?(i.blocks=[...e.blocks],e.text!==void 0&&(i.text=e.text),i):`markdown`in e?(i.markdownText=rewriteBareMentions(e.markdown),i):(i.text=rewriteBareMentions(e.text),i)}function createSlackApiOptions(e){return{token:()=>resolveSlackBotToken(e)}}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){let n=typeof e.text==`string`?e.text:``,r=typeof e.ts==`string`?e.ts:``,i=typeof e.thread_ts==`string`?e.thread_ts:t,a=typeof e.user==`string`?e.user:void 0,o=typeof e.bot_id==`string`?e.bot_id:void 0;return{text:n,markdown:slackMrkdwnToGfm(n),user:a,botId:o,ts:r,threadTs:i,isMe:o!==void 0,raw:e}}export{buildSlackBinding,callSlackApi,resolveSlackBotToken,slackContinuationToken};
@@ -1,17 +1,18 @@
1
1
  /**
2
2
  * Inbound Slack event → harness shaping.
3
3
  *
4
- * The channel calls these helpers on every inbound `app_mention` event
5
- * before handing it to the runtime:
4
+ * The channel calls these helpers on inbound Slack message events before
5
+ * handing them to the runtime:
6
6
  *
7
- * 1. {@link parseAppMentionEvent} parses Slack's webhook envelope into
8
- * a channel-owned {@link SlackMessage}, with the body's text already
9
- * re-rendered as GFM markdown so the agent does not see raw
10
- * `<@U…>` / `<https://…|…>` fragments.
7
+ * 1. {@link slackMessageFromWebhookPayload} converts the shared Chat SDK
8
+ * webhook payload into a channel-owned {@link SlackMessage}. The legacy
9
+ * {@link parseAppMentionEvent} and {@link parseDirectMessageEvent}
10
+ * helpers keep their raw-envelope behavior for direct unit coverage.
11
11
  * 2. The channel renders the actor, message, channel, and thread as one
12
12
  * attributed model message so speaker identity cannot drift away from
13
13
  * the content it describes.
14
14
  */
15
+ import type { SlackAppMentionPayload, SlackDirectMessagePayload } from "#compiled/@chat-adapter/slack/webhook.js";
15
16
  /**
16
17
  * Author metadata for an inbound Slack message. Channel-owned shape;
17
18
  * does not depend on the chat SDK's `Author` interface.
@@ -39,9 +40,9 @@ export interface SlackAttachment {
39
40
  /**
40
41
  * Channel-owned representation of one inbound Slack message.
41
42
  *
42
- * Returned by {@link parseAppMentionEvent} for the triggering mention.
43
- * Replaces the chat SDK `Message` type in the public callback surface
44
- * (e.g. `onAppMention(ctx, message)`).
43
+ * Returned for the triggering Slack event. Replaces the chat SDK
44
+ * `Message` type in the public callback surface (e.g.
45
+ * `onAppMention(ctx, message)`).
45
46
  */
46
47
  export interface SlackMessage {
47
48
  /** The original Slack text (mrkdwn). */
@@ -99,6 +100,7 @@ export declare function parseAppMentionEvent(envelope: SlackEventCallback): Slac
99
100
  * bot's own DM replies from re-triggering the handler.
100
101
  */
101
102
  export declare function parseDirectMessageEvent(envelope: SlackEventCallback): SlackMessage | null;
103
+ export declare function slackMessageFromWebhookPayload(payload: SlackAppMentionPayload | SlackDirectMessagePayload): SlackMessage | null;
102
104
  /**
103
105
  * Verified inbound identity used to attribute a model-visible Slack message.
104
106
  *
@@ -1 +1 @@
1
- import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";function parseAppMentionEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`app_mention`?null:buildSlackMessage(t,e.team_id)}function parseDirectMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;if(!t||t.type!==`message`)return null;let n=t;return n.channel_type!==`im`||typeof n.subtype==`string`&&n.subtype.length>0&&n.subtype!==`file_share`||typeof n.bot_id==`string`&&n.bot_id.length>0?null:buildSlackMessage(n,e.team_id)}function buildSlackMessage(t,n){let r=typeof t.channel==`string`?t.channel:``,i=typeof t.ts==`string`?t.ts:``;if(!r||!i)return null;let a=typeof t.text==`string`?t.text:``,o=typeof t.thread_ts==`string`?t.thread_ts:i,s=typeof n==`string`?n:void 0;return{text:a,markdown:slackMrkdwnToGfm(a),ts:i,threadTs:o,channelId:r,teamId:s,author:parseAuthor(t),attachments:parseAttachments(t.files),raw:t}}function parseAuthor(e){let t=typeof e.user==`string`?e.user:``;if(t)return{userId:t,userName:typeof e.username==`string`?e.username:void 0,fullName:void 0,isBot:typeof e.bot_id==`string`&&e.bot_id.length>0,isMe:!1}}function parseAttachments(e){return Array.isArray(e)?e.map(toAttachment):[]}function toAttachment(e){let t=typeof e.mimetype==`string`?e.mimetype:void 0,n=typeof e.url_private==`string`?e.url_private:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:n,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}function inferAttachmentType(e){return e===void 0?`file`:e.startsWith(`image/`)?`image`:e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:`file`}export{parseAppMentionEvent,parseDirectMessageEvent};
1
+ import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";function parseAppMentionEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`app_mention`?null:buildSlackMessage(t,e.team_id)}function parseDirectMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;if(!t||t.type!==`message`)return null;let n=t;return n.channel_type!==`im`||typeof n.subtype==`string`&&n.subtype.length>0&&n.subtype!==`file_share`||typeof n.bot_id==`string`&&n.bot_id.length>0?null:buildSlackMessage(n,e.team_id)}function slackMessageFromWebhookPayload(t){return t.kind===`direct_message`&&(typeof t.subtype==`string`&&t.subtype.length>0&&t.subtype!==`file_share`||typeof t.botId==`string`&&t.botId.length>0)||!t.channelId||!t.ts?null:{text:t.text,markdown:slackMrkdwnToGfm(t.text),ts:t.ts,threadTs:t.threadTs,channelId:t.channelId,teamId:t.teamId,author:parsePayloadAuthor(t),attachments:parsePayloadAttachments(t.files),raw:t.raw}}function buildSlackMessage(t,n){let r=typeof t.channel==`string`?t.channel:``,i=typeof t.ts==`string`?t.ts:``;if(!r||!i)return null;let a=typeof t.text==`string`?t.text:``,o=typeof t.thread_ts==`string`?t.thread_ts:i,s=typeof n==`string`?n:void 0;return{text:a,markdown:slackMrkdwnToGfm(a),ts:i,threadTs:o,channelId:r,teamId:s,author:parseAuthor(t),attachments:parseAttachments(t.files),raw:t}}function parseAuthor(e){let t=typeof e.user==`string`?e.user:``;if(t)return{userId:t,userName:typeof e.username==`string`?e.username:void 0,fullName:void 0,isBot:typeof e.bot_id==`string`&&e.bot_id.length>0,isMe:!1}}function parseAttachments(e){return Array.isArray(e)?e.map(toAttachment):[]}function toAttachment(e){let t=typeof e.mimetype==`string`?e.mimetype:void 0,n=typeof e.url_private==`string`?e.url_private:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:n,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}function 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,slackMessageFromWebhookPayload};
@@ -16,6 +16,7 @@
16
16
  * `onInteraction` callback. Always returns `Response("ok")` — followup
17
17
  * work runs under `waitUntil` so the webhook ACK is immediate.
18
18
  */
19
+ import { type SlackBlockActionsPayload } from "#compiled/@chat-adapter/slack/webhook.js";
19
20
  import type { SlackChannelConfig, SlackChannelState, SlackInteractionAction } from "#public/channels/slack/slackChannel.js";
20
21
  import type { SendFn } from "#public/definitions/defineChannel.js";
21
22
  /**
@@ -39,7 +40,7 @@ interface ParsedBlockActionsPayload {
39
40
  * Returns `null` for payloads that don't carry the channel/thread
40
41
  * metadata the handler needs.
41
42
  */
42
- export declare function parseBlockActionsPayload(body: Record<string, unknown>): ParsedBlockActionsPayload | null;
43
+ export declare function parseBlockActionsPayload(body: Record<string, unknown> | SlackBlockActionsPayload): ParsedBlockActionsPayload | null;
43
44
  /**
44
45
  * Channel-supplied dependencies for {@link handleInteractionPost}.
45
46
  *
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.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";const log=createLogger(`slack.interactions`);function parseBlockActionsPayload(e){let t=e.actions;if(!Array.isArray(t))return null;let n=e.channel?.id,r=e.message,i=r?.thread_ts??r?.ts;if(!n||!i)return null;let a=e.team,o=e.user,s=a?.id??o.team_id,c={id:o.id,username:o.username,name:o.name},l=r?.blocks??[];return{actions:t.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:r?.ts,label:extractActionLabel(e),user:c})),channelId:n,threadTs:i,teamId:s,messageBlocks:l}}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){for(let t of e)if(typeof t==`object`&&t&&t.type===`section`)return t}function readPromptTextFromBlocks(e){let t=findPromptBlock(e)?.text?.text;return typeof t==`string`&&t.length>0?t:void 0}async function handleInteractionPost(e,n,a){let o=new Response(`ok`,{status:200}),s=new URLSearchParams(e).get(`payload`);if(!s)return o;let c;try{c=JSON.parse(s)}catch{return log.warn(`failed to parse Slack interaction payload`),o}if(c?.type===`view_submission`)return handleViewSubmission(c,n,a);let l=parseBlockActionsPayload(c);if(!l)return o;let d=l.actions.find(e=>isFreeformAction(e.actionId));if(d)return await openFreeformModal({payload:c,interaction:l,freeformAction:d,deps:a}),o;let m=slackContinuationToken(l.channelId,l.threadTs),h=l.actions.map(deriveHitlResponse).filter(e=>e!==null);if(h.length>0){let e=l.actions[0]?.user;if(!e)return o;n.waitUntil(n.send({inputResponses:h},{auth:buildSlackAuthContext({channelId:l.channelId,teamId:l.teamId,threadTs:l.threadTs,userId:e.id,userName:e.username??e.name}),continuationToken:m,state:{channelId:l.channelId,threadTs:l.threadTs,teamId:l.teamId??null,triggeringUserId:e.id}}).catch(e=>{log.error(`HITL interaction delivery failed`,{error:e})})),n.waitUntil(updateAnsweredHitlCard(l,a).catch(e=>{log.error(`HITL answered-card update failed`,{error:e})}))}let g=a.config.onInteraction;if(g){let e=l.actions.filter(e=>!isHitlAction(e.actionId));if(e.length>0){let{thread:r,slack:i}=buildSlackBinding({botToken:a.config.credentials?.botToken,channelId:l.channelId,threadTs:l.threadTs,teamId:l.teamId}),o={thread:r,slack:i};for(let t of e)n.waitUntil(Promise.resolve(g(t,o)).catch(e=>{log.error(`custom interaction handler failed`,{error:e})}))}}return o}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 i=freeformRequestIdFromActionId(e.freeformAction.actionId)??e.freeformAction.value;if(!i){log.warn(`freeform button click missing requestId`);return}let a=e.freeformAction.messageTs;if(!a){log.warn(`freeform button click missing messageTs`);return}let o=buildFreeformModalView({metadata:{continuationToken:slackContinuationToken(e.interaction.channelId,e.interaction.threadTs),channelId:e.interaction.channelId,threadTs:e.interaction.threadTs,messageTs:a,requestId:i},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:o})});c.ok||log.error(`Slack views.open returned non-2xx`,{status:c.status})}async function handleViewSubmission(e,t,n){let r=new Response(null,{status:200}),c=e.view;if(c?.callback_id!==HITL_FREEFORM_MODAL_CALLBACK_ID)return r;let l;try{l=JSON.parse(c.private_metadata??``)}catch{return log.warn(`freeform view_submission carries invalid private_metadata`),r}if(!l.continuationToken||!l.requestId||!l.messageTs||!l.channelId||!l.threadTs)return r;let u=c.state?.values?.[HITL_FREEFORM_MODAL_BLOCK_ID]?.[HITL_FREEFORM_MODAL_ACTION_ID]?.value,d=typeof u==`string`?u:``;if(d.length===0)return r;let f=e.team,p=e.user,m=p.id,h=p.team_id??f?.id??null;return t.waitUntil(t.send({inputResponses:[{requestId:l.requestId,text:d}]},{auth:buildSlackAuthContext({channelId:l.channelId,teamId:h,threadTs:l.threadTs,userId:p.id,userName:p.username??p.name}),continuationToken:l.continuationToken,state:{channelId:l.channelId,threadTs:l.threadTs,teamId:h,triggeringUserId:m}}).catch(e=>{log.error(`freeform answer delivery failed`,{error:e})})),t.waitUntil(updateAnsweredFreeformCard({channelId:l.channelId,messageTs:l.messageTs,answerLabel:d,userId:m??void 0,deps:n}).catch(e=>{log.error(`freeform answered-card update failed`,{error:e})})),r}async function updateAnsweredHitlCard(e,t){let r=e.actions.find(e=>isHitlAction(e.actionId));if(!r||!r.messageTs)return;let i=r.label??r.selectedOptionValue??r.value;if(!i)return;let a=buildAnsweredBlocks({promptBlock:findPromptBlock(e.messageBlocks),answerLabel:i,userId:r.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:r.messageTs,blocks:a,text:`Answered: ${i}`})});if(!s.ok)throw Error(`Slack chat.update returned HTTP ${s.status}`)}async function updateAnsweredFreeformCard(e){let t=buildAnsweredBlocks({promptBlock:void 0,answerLabel:e.answerLabel,userId:e.userId}),r=await resolveSlackBotToken(e.deps.config.credentials?.botToken),i=await fetch(`https://slack.com/api/chat.update`,{method:`POST`,headers:{authorization:`Bearer ${r}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({channel:e.channelId,ts:e.messageTs,blocks:t,text:`Answered: ${e.answerLabel}`})});if(!i.ok)throw Error(`Slack chat.update returned HTTP ${i.status}`)}export{handleInteractionPost,parseBlockActionsPayload};
1
+ import{createLogger}from"#internal/logging.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";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){for(let t of e)if(typeof t==`object`&&t&&t.type===`section`)return t}function readPromptTextFromBlocks(e){let t=findPromptBlock(e)?.text?.text;return typeof t==`string`&&t.length>0?t:void 0}async function handleInteractionPost(e,n,a){let o=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`),o}if(s.kind===`view_submission`)return handleViewSubmission(s,n,a);if(s.kind!==`block_actions`)return o;let c=parseBlockActionsPayload(s);if(!c)return o;let l=c.actions.find(e=>isFreeformAction(e.actionId));if(l)return await openFreeformModal({payload:s.raw,interaction:c,freeformAction:l,deps:a}),o;let d=slackContinuationToken(c.channelId,c.threadTs),p=c.actions.map(deriveHitlResponse).filter(e=>e!==null);if(p.length>0){let e=c.actions[0]?.user;if(!e)return o;n.waitUntil(n.send({inputResponses:p},{auth:buildSlackAuthContext({channelId:c.channelId,teamId:c.teamId,threadTs:c.threadTs,userId:e.id,userName:e.username??e.name}),continuationToken:d,state:{channelId:c.channelId,threadTs:c.threadTs,teamId:c.teamId??null,triggeringUserId:e.id}}).catch(e=>{log.error(`HITL interaction delivery failed`,{error:e})})),n.waitUntil(updateAnsweredHitlCard(c,a).catch(e=>{log.error(`HITL answered-card update failed`,{error:e})}))}let m=a.config.onInteraction;if(m){let e=c.actions.filter(e=>!isHitlAction(e.actionId));if(e.length>0){let{thread:r,slack:i}=buildSlackBinding({botToken:a.config.credentials?.botToken,channelId:c.channelId,threadTs:c.threadTs,teamId:c.teamId}),o={thread:r,slack:i};for(let t of e)n.waitUntil(Promise.resolve(m(t,o)).catch(e=>{log.error(`custom interaction handler failed`,{error:e})}))}}return o}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 i=freeformRequestIdFromActionId(e.freeformAction.actionId)??e.freeformAction.value;if(!i){log.warn(`freeform button click missing requestId`);return}let a=e.freeformAction.messageTs;if(!a){log.warn(`freeform button click missing messageTs`);return}let o=buildFreeformModalView({metadata:{continuationToken:slackContinuationToken(e.interaction.channelId,e.interaction.threadTs),channelId:e.interaction.channelId,threadTs:e.interaction.threadTs,messageTs:a,requestId:i},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:o})});c.ok||log.error(`Slack views.open returned non-2xx`,{status:c.status})}async function handleViewSubmission(e,t,n){let r=new Response(null,{status:200});if(e.callbackId!==HITL_FREEFORM_MODAL_CALLBACK_ID)return r;let c;try{c=JSON.parse(e.privateMetadata??``)}catch{return log.warn(`freeform view_submission carries invalid private_metadata`),r}if(!c.continuationToken||!c.requestId||!c.messageTs||!c.channelId||!c.threadTs)return r;let l=e.values?.find(e=>e.blockId===HITL_FREEFORM_MODAL_BLOCK_ID&&e.actionId===HITL_FREEFORM_MODAL_ACTION_ID)?.value??``;if(l.length===0)return r;let u=e.user,d=e.userId,f=u?.teamId??e.teamId??null;return t.waitUntil(t.send({inputResponses:[{requestId:c.requestId,text:l}]},{auth:buildSlackAuthContext({channelId:c.channelId,teamId:f,threadTs:c.threadTs,userId:d,userName:u?.username??u?.name}),continuationToken:c.continuationToken,state:{channelId:c.channelId,threadTs:c.threadTs,teamId:f,triggeringUserId:d}}).catch(e=>{log.error(`freeform answer delivery failed`,{error:e})})),t.waitUntil(updateAnsweredFreeformCard({channelId:c.channelId,messageTs:c.messageTs,answerLabel:l,userId:d??void 0,deps:n}).catch(e=>{log.error(`freeform answered-card update failed`,{error:e})})),r}async function updateAnsweredHitlCard(e,t){let r=e.actions.find(e=>isHitlAction(e.actionId));if(!r||!r.messageTs)return;let i=r.label??r.selectedOptionValue??r.value;if(!i)return;let a=buildAnsweredBlocks({promptBlock:findPromptBlock(e.messageBlocks),answerLabel:i,userId:r.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:r.messageTs,blocks:a,text:`Answered: ${i}`})});if(!s.ok)throw Error(`Slack chat.update returned HTTP ${s.status}`)}async function updateAnsweredFreeformCard(e){let t=buildAnsweredBlocks({promptBlock:void 0,answerLabel:e.answerLabel,userId:e.userId}),r=await resolveSlackBotToken(e.deps.config.credentials?.botToken),i=await fetch(`https://slack.com/api/chat.update`,{method:`POST`,headers:{authorization:`Bearer ${r}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({channel:e.channelId,ts:e.messageTs,blocks:t,text:`Answered: ${e.answerLabel}`})});if(!i.ok)throw Error(`Slack chat.update returned HTTP ${i.status}`)}export{handleInteractionPost,parseBlockActionsPayload};
@@ -1,38 +1,6 @@
1
- /**
2
- * Slack mrkdwn ↔ GitHub-flavored markdown converters and the bare
3
- * `@mention` rewriter used by the outbound post pipeline. These are
4
- * pure string utilities — no Slack API I/O, no I/O at all — so they
5
- * live separately from the binding constructor and request shape code
6
- * in {@link ./api.ts}.
7
- */
8
- /**
9
- * Rewrites bare `@USER_ID` tokens (the form Slack apps and humans tend
10
- * to type) into the `<@USER_ID>` mention syntax Slack actually renders.
11
- * Anything already wrapped in `<...>` is left untouched.
12
- */
1
+ /** Rewrites bare Slack mention tokens into Slack's linked mention syntax. */
13
2
  export declare function rewriteBareMentions(text: string): string;
14
- /**
15
- * Best-effort GFM → Slack mrkdwn converter used only in contexts that
16
- * do not support `markdown_text` (e.g. `files.completeUploadExternal`'s
17
- * `initial_comment` field).
18
- *
19
- * The main `{ markdown }` post path sends `markdown_text` directly
20
- * to `chat.postMessage` and does not go through this converter.
21
- */
3
+ /** Converts markdown into Slack mrkdwn for legacy Slack text surfaces. */
22
4
  export declare function gfmToSlackMrkdwn(input: string): string;
23
- /**
24
- * Best-effort Slack mrkdwn → GFM converter applied to the text of
25
- * every inbound Slack message before the harness sees it.
26
- *
27
- * - `<@U123>` → `@U123`
28
- * - `<#C123|name>` → `#name` (or `#C123` when no name)
29
- * - `<!channel>` etc. → `@channel`
30
- * - `<https://x|label>` → `[label](https://x)`
31
- * - `<https://x>` → `https://x`
32
- * - `*bold*` (paired) → `**bold**`
33
- * - `~strike~` (paired) → `~~strike~~`
34
- *
35
- * Inline `_italic_` and code spans pass through unchanged because both
36
- * formats render them identically.
37
- */
5
+ /** Converts inbound Slack mrkdwn into markdown while preserving code spans and fences. */
38
6
  export declare function slackMrkdwnToGfm(input: string): string;
@@ -1 +1 @@
1
- const BARE_MENTION_RE=/(?<![<\w])@(\w+)/gu;function rewriteBareMentions(t){return t.replace(BARE_MENTION_RE,`<@$1>`)}function gfmToSlackMrkdwn(e){return splitCodeFences(e).map(e=>e.kind===`code`?e.text:convertInline(e.text)).join(``)}function slackMrkdwnToGfm(e){return splitCodeFences(e).map(e=>e.kind===`code`?e.text:decodeInline(e.text)).join(``)}function splitCodeFences(e){let t=[],n=/```[\s\S]*?```|`[^`\n]+`/gu,r=0;for(let i of e.matchAll(n)){let n=i.index??0;n>r&&t.push({kind:`text`,text:e.slice(r,n)}),t.push({kind:`code`,text:i[0]}),r=n+i[0].length}return r<e.length&&t.push({kind:`text`,text:e.slice(r)}),t}function convertInline(e){let t=e;return t=t.replace(/\*\*([^*\n]+)\*\*/gu,`*$1*`),t=t.replace(/__([^_\n]+)__/gu,`*$1*`),t=t.replace(/~~([^~\n]+)~~/gu,`~$1~`),t=t.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/gu,`<$2|$1>`),t}function decodeInline(e){let t=e;return t=t.replace(/<!(channel|here|everyone)>/gu,`@$1`),t=t.replace(/<@([A-Z0-9]+)\|([^>]+)>/gu,`@$2`),t=t.replace(/<@([A-Z0-9]+)>/gu,`@$1`),t=t.replace(/<#([A-Z0-9]+)\|([^>]+)>/gu,`#$2`),t=t.replace(/<#([A-Z0-9]+)>/gu,`#$1`),t=t.replace(/<(https?:\/\/[^|>\s]+)\|([^>]+)>/gu,`[$2]($1)`),t=t.replace(/<(https?:\/\/[^>\s]+)>/gu,`$1`),t=t.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/gu,`$1**$2**`),t=t.replace(/(^|[^~])~([^~\n]+)~(?!~)/gu,`$1~~$2~~`),t}export{gfmToSlackMrkdwn,rewriteBareMentions,slackMrkdwnToGfm};
1
+ import{formatSlackLink,linkBareSlackMentions,markdownBoldToSlackMrkdwn,slackMrkdwnToMarkdown}from"#compiled/@chat-adapter/slack/format.js";const BARE_MENTION_RE=/(?<![<\w])@(\w+)/gu;function rewriteBareMentions(e){return linkBareSlackMentions(e).replace(BARE_MENTION_RE,`<@$1>`)}function gfmToSlackMrkdwn(e){return splitCodeFences(e).map(e=>e.kind===`code`?e.text:markdownToSlack(e.text)).join(``)}function slackMrkdwnToGfm(e){return splitCodeFences(e).map(e=>e.kind===`code`?e.text:slackToMarkdown(e.text)).join(``)}function splitCodeFences(e){let t=[],n=/```[\s\S]*?```|`[^`\n]+`/gu,r=0;for(let i of e.matchAll(n)){let n=i.index??0;n>r&&t.push({kind:`text`,text:e.slice(r,n)}),t.push({kind:`code`,text:i[0]}),r=n+i[0].length}return r<e.length&&t.push({kind:`text`,text:e.slice(r)}),t}function markdownToSlack(e){let t=markdownBoldToSlackMrkdwn(e);return t=t.replace(/__([^_\n]+)__/gu,`*$1*`),t=t.replace(/~~([^~\n]+)~~/gu,`~$1~`),t=t.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/gu,(e,t,n)=>formatMarkdownLink(e,t,n)),t}function slackToMarkdown(e){return slackMrkdwnToMarkdown(e.replace(/<!(channel|here|everyone)>/gu,`@$1`))}function formatMarkdownLink(t,n,r){try{return formatSlackLink(r,n)}catch(e){if(e instanceof TypeError)return t;throw e}}export{gfmToSlackMrkdwn,rewriteBareMentions,slackMrkdwnToGfm};
@@ -1 +1 @@
1
- import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,slackContinuationToken}from"#public/channels/slack/api.js";import{slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{parseAppMentionEvent,parseDirectMessageEvent}from"#public/channels/slack/inbound.js";import{formatSlackInboundMessage,formatSlackThreadContext}from"#public/channels/slack/model-context.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=createSlackFetchFile({botToken:e.credentials?.botToken}),u=e.onAppMention??defaultOnAppMention,d=e.onDirectMessage??defaultOnDirectMessage,f=e.events?.[`authorization.required`],p=e.events?.[`turn.started`]??defaultEvents[`turn.started`],m={...defaultEvents,...e.events,async"turn.started"(e,t,n){let r=slackUserIdFromAuthContext(n.session.auth.current);r!==void 0&&(t.state.triggeringUserId=r),await p(e,t,n)},"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(),"authorization.required":f===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(f)},h=new Set;return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,lastReasoningTypingAtMs:null,lastReasoningTypingStatus:null,pendingAuthMessageTs:{}},fetchFile:n,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{send:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials);return a===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(n.headers)?new Response(`ok`):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(a,{send:r,waitUntil:i},{config:e}):handleEventPost({body:a,send:r,waitUntil:i,onAppMention:u,onDirectMessage:d,uploadPolicy:t,threadContext:e.threadContext,handledEvents:h,headers:n.headers,credentials:e.credentials})})],async receive(t,{send:n}){let r=t.target,i=r.channelId;if(!i||typeof i!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let a=typeof r.threadTs==`string`?r.threadTs:``,s=r.initialMessage;if(s&&a.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=a;if(s){let{thread:t}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:i,threadTs:``,teamId:void 0}),n={card:s.card};s.fallbackText!==void 0&&(n.fallbackText=s.fallbackText),c=(await t.post(n)).id}let l=c||crypto.randomUUID();return n(t.message,{auth:t.auth,continuationToken:slackContinuationToken(i,l),state:{channelId:i,threadTs:c||null,teamId:null,triggeringUserId:null}})},events:m})}function constrainAuthorizationRequired(e){return(t,n,r)=>e(t,{postEphemeral:(e,t)=>n.thread.postEphemeral(e,t),postDirectMessage:(e,t)=>n.thread.postDirectMessage(e,t),state:n.state},r)}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(t){let n;try{n=JSON.parse(t.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(typeof n.challenge==`string`)return new Response(n.challenge,{status:200,headers:{"content-type":`text/plain`}});if(n.event_id){if(t.handledEvents.has(n.event_id))return log.warn(`received a duplicate event`,{event_id:n.event_id,event_time:n.event_time,retry_num:t.headers.get(`x-slack-retry-num`)||`(null)`,retry_reason:t.headers.get(`x-slack-retry-reason`)||`(null)`}),new Response(`ok`);markEventHandled(n.event_id,t.handledEvents)}let r=parseAppMentionEvent(n);if(r)return t.waitUntil(dispatchInboundMessage({kind:`app_mention`,message:r,handler:t.onAppMention,send:t.send,uploadPolicy:t.uploadPolicy,threadContext:t.threadContext,credentials:t.credentials})),new Response(`ok`);let i=parseDirectMessageEvent(n);return i&&t.waitUntil(dispatchInboundMessage({kind:`direct_message`,message:i,handler:t.onDirectMessage,send:t.send,uploadPolicy:t.uploadPolicy,threadContext:t.threadContext,credentials:t.credentials})),new Response(`ok`)}async function verifyInbound(e,t){try{return await verifySlackRequest(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function dispatchInboundMessage(e){let{message:t,kind:r}=e,{thread:i,slack:a}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId}),s={thread:i,slack:a},c;try{c=await e.handler(s,t)}catch(e){logError(log,`${r} handler failed`,e,{channelId:t.channelId});return}if(c!=null)try{let n=formatSlackThreadContext(e.threadContext===void 0?[]:await loadThreadContextMessages(i,t,e.threadContext)),r=await collectInboundFileParts({mention:t,thread:i,policy:e.uploadPolicy}),a={channelId:t.channelId,fullName:t.author?.fullName,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},o=formatSlackInboundMessage(a,t),s=buildSlackTurnMessage(n===void 0?o:`${n}\n\n${o}`,r),l=c.context??[];await e.send(l.length===0?{message:s}:{message:s,context:l},{auth:c.auth,continuationToken:slackContinuationToken(t.channelId,t.threadTs),state:{channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId??null,triggeringUserId:a.userId||null},title:t.markdown})}catch(e){logError(log,`${r} delivery failed`,e,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};
1
+ import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,slackContinuationToken}from"#public/channels/slack/api.js";import{slackUserIdFromAuthContext}from"#public/channels/slack/auth.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{loadThreadContextMessages}from"#public/channels/slack/thread.js";import{parseSlackWebhookBody}from"#compiled/@chat-adapter/slack/webhook.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{slackMessageFromWebhookPayload}from"#public/channels/slack/inbound.js";import{formatSlackInboundMessage,formatSlackThreadContext}from"#public/channels/slack/model-context.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest as verifySlackRequest$1}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=createSlackFetchFile({botToken:e.credentials?.botToken}),u=e.onAppMention??defaultOnAppMention,d=e.onDirectMessage??defaultOnDirectMessage,f=e.events?.[`authorization.required`],p=e.events?.[`turn.started`]??defaultEvents[`turn.started`],m={...defaultEvents,...e.events,async"turn.started"(e,t,n){let r=slackUserIdFromAuthContext(n.session.auth.current);r!==void 0&&(t.state.triggeringUserId=r),await p(e,t,n)},"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(),"authorization.required":f===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(f)},h=new Set;return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,lastReasoningTypingAtMs:null,lastReasoningTypingStatus:null,pendingAuthMessageTs:{}},fetchFile:n,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{send:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials);return a===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(n.headers)?new Response(`ok`):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(a,{send:r,waitUntil:i},{config:e}):handleEventPost({body:a,send:r,waitUntil:i,onAppMention:u,onDirectMessage:d,uploadPolicy:t,threadContext:e.threadContext,handledEvents:h,headers:n.headers,credentials:e.credentials})})],async receive(t,{send:n}){let r=t.target,i=r.channelId;if(!i||typeof i!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let a=typeof r.threadTs==`string`?r.threadTs:``,s=r.initialMessage;if(s&&a.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=a;if(s){let{thread:t}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:i,threadTs:``,teamId:void 0}),n={card:s.card};s.fallbackText!==void 0&&(n.fallbackText=s.fallbackText),c=(await t.post(n)).id}let l=c||crypto.randomUUID();return n(t.message,{auth:t.auth,continuationToken:slackContinuationToken(i,l),state:{channelId:i,threadTs:c||null,teamId:null,triggeringUserId:null}})},events:m})}function constrainAuthorizationRequired(e){return(t,n,r)=>e(t,{postEphemeral:(e,t)=>n.thread.postEphemeral(e,t),postDirectMessage:(e,t)=>n.thread.postDirectMessage(e,t),state:n.state},r)}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(t){let n;try{n=parseSlackWebhookBody(t.body,{headers:t.headers})}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(n.kind===`url_verification`)return new Response(n.challenge,{status:200,headers:{"content-type":`text/plain`}});if(n.kind===`unsupported`||n.kind!==`app_mention`&&n.kind!==`direct_message`)return new Response(`ok`);if(n.eventId){if(t.handledEvents.has(n.eventId))return log.warn(`received a duplicate event`,{event_id:n.eventId,event_time:n.eventTime,retry_num:n.retry?.num??`(null)`,retry_reason:n.retry?.reason??`(null)`}),new Response(`ok`);markEventHandled(n.eventId,t.handledEvents)}let r=slackMessageFromWebhookPayload(n);return r?n.kind===`app_mention`?(t.waitUntil(dispatchInboundMessage({kind:`app_mention`,message:r,handler:t.onAppMention,send:t.send,uploadPolicy:t.uploadPolicy,threadContext:t.threadContext,credentials:t.credentials})),new Response(`ok`)):(n.kind===`direct_message`&&t.waitUntil(dispatchInboundMessage({kind:`direct_message`,message:r,handler:t.onDirectMessage,send:t.send,uploadPolicy:t.uploadPolicy,threadContext:t.threadContext,credentials:t.credentials})),new Response(`ok`)):new Response(`ok`)}async function verifyInbound(e,t){try{return await verifySlackRequest$1(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function dispatchInboundMessage(e){let{message:t,kind:r}=e,{thread:i,slack:a}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId}),s={thread:i,slack:a},c;try{c=await e.handler(s,t)}catch(e){logError(log,`${r} handler failed`,e,{channelId:t.channelId});return}if(c!=null)try{let n=formatSlackThreadContext(e.threadContext===void 0?[]:await loadThreadContextMessages(i,t,e.threadContext)),r=await collectInboundFileParts({mention:t,thread:i,policy:e.uploadPolicy}),a={channelId:t.channelId,fullName:t.author?.fullName,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},o=formatSlackInboundMessage(a,t),s=buildSlackTurnMessage(n===void 0?o:`${n}\n\n${o}`,r),l=c.context??[];await e.send(l.length===0?{message:s}:{message:s,context:l},{auth:c.auth,continuationToken:slackContinuationToken(t.channelId,t.threadTs),state:{channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId??null,triggeringUserId:a.userId||null},title:t.markdown})}catch(e){logError(log,`${r} delivery failed`,e,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};
@@ -1,48 +1,10 @@
1
- /**
2
- * Slack inbound-webhook verification.
3
- *
4
- * The native channel replaces `createSlackAdapter`'s built-in HMAC
5
- * check with a self-contained verifier:
6
- *
7
- * 1. If the caller supplied a {@link SlackWebhookVerifier} (e.g.
8
- * Connect's OIDC-based forwarder), delegate to it.
9
- * 2. Otherwise, recompute the Slack v0 HMAC and compare with
10
- * `X-Slack-Signature`. Rejects requests older than five minutes to
11
- * foil replay attacks.
12
- *
13
- * Returns the verified raw body string on success so the caller can
14
- * parse it without re-reading the request stream.
15
- */
16
- /**
17
- * Caller-supplied inbound webhook verifier. Used as an alternative to
18
- * HMAC verification — e.g. Connect supplies a verifier that
19
- * authenticates Connect-forwarded webhooks with Vercel OIDC instead
20
- * of Slack's signing secret.
21
- *
22
- * Contract (matches the chat SDK's `SlackAdapterConfig.webhookVerifier`):
23
- *
24
- * - **Throw / reject** → the channel responds 401 to Slack.
25
- * - **Return a falsy value** (`null` / `undefined` / `false` / `""` /
26
- * `0`) → the channel responds 401 to Slack. This lets verifiers
27
- * signal rejection without throwing — e.g. Connect's `vercelOidc()`
28
- * returns `null` on a failed OIDC check.
29
- * - **Return a truthy non-string value** → verification accepted.
30
- * - **Return a string** → verification accepted, and the string
31
- * replaces the raw body for downstream parsing. Useful when the
32
- * verifier canonicalizes or substitutes the verified payload.
33
- */
34
- export type SlackWebhookVerifier = (request: Request, body: string) => unknown | Promise<unknown>;
1
+ import { type SlackWebhookVerifier } from "#compiled/@chat-adapter/slack/webhook.js";
2
+ export type { SlackWebhookVerifier };
3
+ /** Verification inputs for Eve's Slack webhook route. */
35
4
  export interface SlackVerifyOptions {
36
5
  readonly signingSecret: string | undefined;
37
6
  readonly webhookVerifier: SlackWebhookVerifier | undefined;
38
- /** Max allowed clock skew, in seconds. Defaults to 5 minutes. */
39
7
  readonly maxSkewSeconds?: number;
40
8
  }
41
- /**
42
- * Verifies an inbound Slack webhook and returns its raw body.
43
- *
44
- * Throws when neither a signing secret nor a custom verifier is
45
- * available, when signature/timestamp checks fail, or when the
46
- * caller-supplied verifier rejects.
47
- */
9
+ /** Verifies a Slack request with the Chat SDK webhook primitive. */
48
10
  export declare function verifySlackRequest(request: Request, options: SlackVerifyOptions): Promise<string>;
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{createHmac,timingSafeEqual}from"node:crypto";const log=createLogger(`slack.verify`);async function verifySlackRequest(e,n){let r=await e.text();if(n.webhookVerifier!==void 0){let t=await n.webhookVerifier(e,r);if(!t)throw Error(`slackChannel: inbound webhook verifier rejected the request.`);return typeof t==`string`?t:r}if(!n.signingSecret)throw Error(`slackChannel: missing signing secret. Pass credentials.signingSecret, set SLACK_SIGNING_SECRET, or supply credentials.webhookVerifier.`);let i=e.headers.get(`x-slack-request-timestamp`)??``,a=e.headers.get(`x-slack-signature`)??``;if(!i||!a)throw Error(`slackChannel: inbound request missing Slack signature headers.`);let o=Number(i);if(!Number.isFinite(o))throw Error(`slackChannel: inbound request has malformed timestamp.`);let s=n.maxSkewSeconds??300,c=Math.floor(Date.now()/1e3);if(Math.abs(c-o)>s)throw Error(`slackChannel: inbound request timestamp outside allowed skew.`);let l=`v0:${i}:${r}`;if(!constantTimeCompare(`v0=${createHmac(`sha256`,n.signingSecret).update(l).digest(`hex`)}`,a))throw Error(`slackChannel: inbound request signature mismatch.`);return r}function constantTimeCompare(e,t){if(e.length!==t.length)return!1;try{return timingSafeEqual(Buffer.from(e),Buffer.from(t))}catch(e){return log.debug(`timingSafeEqual threw`,{error:e}),!1}}export{verifySlackRequest};
1
+ import{verifySlackRequest as verifySlackRequest$1}from"#compiled/@chat-adapter/slack/webhook.js";async function verifySlackRequest(t,n){if(n.webhookVerifier===void 0&&!n.signingSecret)throw Error(`slackChannel: missing signing secret. Pass credentials.signingSecret, set SLACK_SIGNING_SECRET, or supply credentials.webhookVerifier.`);try{return await verifySlackRequest$1(t,n)}catch(e){throw normalizeSlackWebhookError(e)}}function normalizeSlackWebhookError(e){let t=e instanceof Error?e.message:String(e);return t.includes(`verifier rejected`)?Error(`slackChannel: inbound webhook verifier rejected the request.`):t.includes(`signature headers`)?Error(`slackChannel: inbound request missing Slack signature headers.`):t.includes(`timestamp is invalid`)?Error(`slackChannel: inbound request has malformed timestamp.`):t.includes(`timestamp is too old`)?Error(`slackChannel: inbound request timestamp outside allowed skew.`):t.includes(`signature is invalid`)?Error(`slackChannel: inbound request signature mismatch.`):e instanceof Error?e:Error(t)}export{verifySlackRequest};
@@ -1,6 +1,6 @@
1
1
  import type { PublicAgentDefinition } from "#shared/agent-definition.js";
2
2
  import type { ExactDefinition } from "#public/definitions/exact.js";
3
- export type { AgentModelOptionsDefinition, AgentReasoningDefinition, AgentBuildDefinition, AgentExperimentalDefinition, AgentWorkflowDefinition, AgentWorkflowWorldDefinition, PublicAgentModelDefinition as AgentModelDefinition, PublicAgentCompactionDefinition as AgentCompactionDefinition, } from "#shared/agent-definition.js";
3
+ export type { AgentModelOptionsDefinition, AgentReasoningDefinition, AgentBuildDefinition, AgentExperimentalDefinition, AgentLimitsDefinition, AgentWorkflowDefinition, AgentWorkflowWorldDefinition, PublicAgentModelDefinition as AgentModelDefinition, PublicAgentCompactionDefinition as AgentCompactionDefinition, } from "#shared/agent-definition.js";
4
4
  /**
5
5
  * Additive public agent configuration authored in `agent.ts`.
6
6
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Core agent authoring helpers for `agent/agent.ts`.
3
3
  */
4
- export { type AgentCompactionDefinition, type AgentDefinition, type AgentExperimentalDefinition, type AgentModelDefinition, type AgentModelOptionsDefinition, type AgentReasoningDefinition, type AgentWorkflowDefinition, type AgentWorkflowWorldDefinition, defineAgent, } from "#public/definitions/agent.js";
4
+ export { type AgentCompactionDefinition, type AgentDefinition, type AgentExperimentalDefinition, type AgentLimitsDefinition, type AgentModelDefinition, type AgentModelOptionsDefinition, type AgentReasoningDefinition, type AgentWorkflowDefinition, type AgentWorkflowWorldDefinition, defineAgent, } from "#public/definitions/agent.js";
5
5
  export { type RemoteAgentDefinition, type RemoteAgentDefinitionInput, defineRemoteAgent, } from "#public/definitions/remote-agent.js";
@@ -1 +1 @@
1
- import{ResolveAgentError,createResolvedModuleSourceRef}from"#runtime/resolve-helpers.js";import{resolveChannelDefinition}from"#runtime/resolve-channel.js";import{resolveConnectionDefinition}from"#runtime/resolve-connection.js";import{resolveHookDefinition}from"#runtime/resolve-hook.js";import{resolveSandboxDefinition}from"#runtime/resolve-sandbox.js";import{resolveDynamicInstructionsDefinition}from"#runtime/resolve-dynamic-instructions.js";import{resolveDynamicSkillDefinition}from"#runtime/resolve-dynamic-skill.js";import{resolveDynamicToolDefinition}from"#runtime/resolve-dynamic-tool.js";import{resolveToolDefinition}from"#runtime/resolve-tool.js";async function resolveAgent(e){let t=e.manifest.skills.map(e=>({...e,metadata:e.metadata===void 0?void 0:{...e.metadata}})),r=[],i=[];for(let t of e.manifest.channels){if(t.kind===`disabled`){i.push(t.name);continue}r.push(await resolveChannelDefinition(t,e.moduleMap,e.nodeId))}let a=await Promise.all(e.manifest.tools.map(t=>resolveToolDefinition(t,e.moduleMap,e.nodeId))),o=await Promise.all((e.manifest.dynamicInstructions??[]).map(t=>resolveDynamicInstructionsDefinition(t,e.moduleMap,e.nodeId))),s=await Promise.all((e.manifest.dynamicSkills??[]).map(t=>resolveDynamicSkillDefinition(t,e.moduleMap,e.nodeId))),c=await Promise.all(e.manifest.dynamicTools.map(t=>resolveDynamicToolDefinition(t,e.moduleMap,e.nodeId))),l=await Promise.all(e.manifest.hooks.map(t=>resolveHookDefinition(t,e.moduleMap,e.nodeId))),u=await Promise.all(e.manifest.connections.map(t=>resolveConnectionDefinition(t,e.moduleMap,e.nodeId))),d=e.manifest.sandbox===null?null:await resolveSandboxDefinition(e.manifest.sandbox,e.moduleMap,e.nodeId),f=createResolvedInstructions(e.manifest.instructions),p=e.manifest.workspaceResourceRoot,m={channels:r,config:createResolvedAgentConfig(e.manifest),connections:u,disabledFrameworkChannels:i,disabledFrameworkTools:[...e.manifest.disabledFrameworkTools],workflowEnabled:e.manifest.workflowEnabled,dynamicInstructionsResolvers:o,dynamicSkillResolvers:s,dynamicToolResolvers:c,hooks:l,metadata:{agentRoot:e.manifest.agentRoot,appRoot:e.manifest.appRoot,diagnosticsSummary:e.manifest.diagnosticsSummary},sandbox:d,workspaceResourceRoot:p,skills:t,tools:a,workspaceSpec:{rootEntries:[...p.rootEntries]}};return f===void 0?m:{...m,instructions:f}}function createResolvedInstructions(e){if(e!==void 0)return{name:e.name,logicalPath:e.logicalPath,markdown:e.markdown,sourceId:e.sourceId,sourceKind:e.sourceKind}}function createResolvedAgentConfig(e){let n={model:e.config.model.source===void 0?{id:e.config.model.id,contextWindowTokens:e.config.model.contextWindowTokens,providerOptions:e.config.model.providerOptions}:{contextWindowTokens:e.config.model.contextWindowTokens,id:e.config.model.id,providerOptions:e.config.model.providerOptions,source:{exportName:e.config.model.source.exportName,sourceKind:`module`,logicalPath:e.config.model.source.logicalPath,sourceId:e.config.model.source.sourceId}},name:e.config.name};if(e.config.compaction!==void 0){let t={};e.config.compaction.model!==void 0&&(t.model=e.config.compaction.model.source===void 0?{contextWindowTokens:e.config.compaction.model.contextWindowTokens,id:e.config.compaction.model.id,providerOptions:e.config.compaction.model.providerOptions}:{contextWindowTokens:e.config.compaction.model.contextWindowTokens,id:e.config.compaction.model.id,providerOptions:e.config.compaction.model.providerOptions,source:{exportName:e.config.compaction.model.source.exportName,sourceKind:`module`,logicalPath:e.config.compaction.model.source.logicalPath,sourceId:e.config.compaction.model.source.sourceId}}),e.config.compaction.thresholdPercent!==void 0&&(t.thresholdPercent=e.config.compaction.thresholdPercent),n.compaction=t}return e.config.experimental!==void 0&&(n.experimental={workflow:e.config.experimental.workflow===void 0?void 0:{world:e.config.experimental.workflow.world}}),e.config.outputSchema!==void 0&&(n.outputSchema=e.config.outputSchema),e.config.reasoning!==void 0&&(n.reasoning=e.config.reasoning),e.config.source!==void 0&&(n.source=createResolvedModuleSourceRef(e.config.source)),n}export{ResolveAgentError,resolveAgent};
1
+ import{ResolveAgentError,createResolvedModuleSourceRef}from"#runtime/resolve-helpers.js";import{resolveChannelDefinition}from"#runtime/resolve-channel.js";import{resolveConnectionDefinition}from"#runtime/resolve-connection.js";import{resolveHookDefinition}from"#runtime/resolve-hook.js";import{resolveSandboxDefinition}from"#runtime/resolve-sandbox.js";import{resolveDynamicInstructionsDefinition}from"#runtime/resolve-dynamic-instructions.js";import{resolveDynamicSkillDefinition}from"#runtime/resolve-dynamic-skill.js";import{resolveDynamicToolDefinition}from"#runtime/resolve-dynamic-tool.js";import{resolveToolDefinition}from"#runtime/resolve-tool.js";async function resolveAgent(e){let t=e.manifest.skills.map(e=>({...e,metadata:e.metadata===void 0?void 0:{...e.metadata}})),r=[],i=[];for(let t of e.manifest.channels){if(t.kind===`disabled`){i.push(t.name);continue}r.push(await resolveChannelDefinition(t,e.moduleMap,e.nodeId))}let a=await Promise.all(e.manifest.tools.map(t=>resolveToolDefinition(t,e.moduleMap,e.nodeId))),o=await Promise.all((e.manifest.dynamicInstructions??[]).map(t=>resolveDynamicInstructionsDefinition(t,e.moduleMap,e.nodeId))),s=await Promise.all((e.manifest.dynamicSkills??[]).map(t=>resolveDynamicSkillDefinition(t,e.moduleMap,e.nodeId))),c=await Promise.all(e.manifest.dynamicTools.map(t=>resolveDynamicToolDefinition(t,e.moduleMap,e.nodeId))),l=await Promise.all(e.manifest.hooks.map(t=>resolveHookDefinition(t,e.moduleMap,e.nodeId))),u=await Promise.all(e.manifest.connections.map(t=>resolveConnectionDefinition(t,e.moduleMap,e.nodeId))),d=e.manifest.sandbox===null?null:await resolveSandboxDefinition(e.manifest.sandbox,e.moduleMap,e.nodeId),f=createResolvedInstructions(e.manifest.instructions),p=e.manifest.workspaceResourceRoot,m={channels:r,config:createResolvedAgentConfig(e.manifest),connections:u,disabledFrameworkChannels:i,disabledFrameworkTools:[...e.manifest.disabledFrameworkTools],workflowEnabled:e.manifest.workflowEnabled,dynamicInstructionsResolvers:o,dynamicSkillResolvers:s,dynamicToolResolvers:c,hooks:l,metadata:{agentRoot:e.manifest.agentRoot,appRoot:e.manifest.appRoot,diagnosticsSummary:e.manifest.diagnosticsSummary},sandbox:d,workspaceResourceRoot:p,skills:t,tools:a,workspaceSpec:{rootEntries:[...p.rootEntries]}};return f===void 0?m:{...m,instructions:f}}function createResolvedInstructions(e){if(e!==void 0)return{name:e.name,logicalPath:e.logicalPath,markdown:e.markdown,sourceId:e.sourceId,sourceKind:e.sourceKind}}function createResolvedAgentConfig(e){let n={model:e.config.model.source===void 0?{id:e.config.model.id,contextWindowTokens:e.config.model.contextWindowTokens,providerOptions:e.config.model.providerOptions}:{contextWindowTokens:e.config.model.contextWindowTokens,id:e.config.model.id,providerOptions:e.config.model.providerOptions,source:{exportName:e.config.model.source.exportName,sourceKind:`module`,logicalPath:e.config.model.source.logicalPath,sourceId:e.config.model.source.sourceId}},name:e.config.name};if(e.config.compaction!==void 0){let t={};e.config.compaction.model!==void 0&&(t.model=e.config.compaction.model.source===void 0?{contextWindowTokens:e.config.compaction.model.contextWindowTokens,id:e.config.compaction.model.id,providerOptions:e.config.compaction.model.providerOptions}:{contextWindowTokens:e.config.compaction.model.contextWindowTokens,id:e.config.compaction.model.id,providerOptions:e.config.compaction.model.providerOptions,source:{exportName:e.config.compaction.model.source.exportName,sourceKind:`module`,logicalPath:e.config.compaction.model.source.logicalPath,sourceId:e.config.compaction.model.source.sourceId}}),e.config.compaction.thresholdPercent!==void 0&&(t.thresholdPercent=e.config.compaction.thresholdPercent),n.compaction=t}return e.config.experimental!==void 0&&(n.experimental={workflow:e.config.experimental.workflow===void 0?void 0:{world:e.config.experimental.workflow.world}}),e.config.outputSchema!==void 0&&(n.outputSchema=e.config.outputSchema),e.config.reasoning!==void 0&&(n.reasoning=e.config.reasoning),e.config.source!==void 0&&(n.source=createResolvedModuleSourceRef(e.config.source)),e.config.limits!==void 0&&(n.limits={maxSubagentDepth:e.config.limits.maxSubagentDepth,maxInputTokensPerSession:e.config.limits.maxInputTokensPerSession,maxOutputTokensPerSession:e.config.limits.maxOutputTokensPerSession}),n}export{ResolveAgentError,resolveAgent};
@@ -1,18 +1,22 @@
1
1
  import type { ClientOptions } from "#client/index.js";
2
2
  import type { DevelopmentCredentialGate } from "./credential-gate.js";
3
+ type DevelopmentClientHeaders = Readonly<Record<string, string>>;
3
4
  /**
4
5
  * Builds anonymous {@link ClientOptions} for a development target. Locality is
5
6
  * not an authorization decision, so remote URLs receive no ambient Vercel
6
7
  * credentials through this default path.
7
8
  */
8
9
  export declare function resolveDevelopmentClientOptions(serverUrl: string): ClientOptions;
9
- /** Builds a non-redirecting local client with an explicit per-request bearer source. */
10
+ /** Builds a non-redirecting local client, using ambient bearer auth only when it owns Authorization. */
10
11
  export declare function resolveLocalDevelopmentClientOptions(input: {
12
+ readonly headers?: DevelopmentClientHeaders;
11
13
  readonly serverUrl: string;
12
14
  readonly token: () => Promise<string>;
13
15
  }): ClientOptions;
14
16
  /** Builds non-redirecting client options backed by one verified credential gate. */
15
17
  export declare function resolveRemoteDevelopmentClientOptions(input: {
16
18
  readonly credentials: DevelopmentCredentialGate;
19
+ readonly headers?: DevelopmentClientHeaders;
17
20
  readonly serverUrl: string;
18
21
  }): ClientOptions;
22
+ export {};
@@ -1 +1 @@
1
- function resolveDevelopmentClientOptions(e){return{host:e}}function resolveLocalDevelopmentClientOptions(e){return{auth:{bearer:e.token},host:e.serverUrl,redirect:`manual`}}function resolveRemoteDevelopmentClientOptions(e){let t=new URL(e.serverUrl).origin;if(e.credentials.serverOrigin!==t)throw Error(`Credential gate origin ${e.credentials.serverOrigin} does not match client origin ${t}.`);return{auth:{vercelOidc:{token:()=>e.credentials.resolveToken()}},headers:e.credentials.resolveBypassHeaders,host:e.serverUrl,redirect:`manual`}}export{resolveDevelopmentClientOptions,resolveLocalDevelopmentClientOptions,resolveRemoteDevelopmentClientOptions};
1
+ import{VERCEL_TRUSTED_OIDC_IDP_TOKEN_HEADER}from"#client/types.js";function hasAuthorizationHeader(e){return e!==void 0&&Object.keys(e).some(e=>e.toLowerCase()===`authorization`)}async function resolveRemoteHeaders(t){if(!t.includeTrustedOidcHeader)return{...await t.credentials.resolveBypassHeaders(),...t.headers};let[n,r]=await Promise.all([t.credentials.resolveBypassHeaders(),t.credentials.resolveToken()]),i={...n,...t.headers},a=r.trim();return a.length>0&&(i[VERCEL_TRUSTED_OIDC_IDP_TOKEN_HEADER]=a),i}function resolveDevelopmentClientOptions(e){return{host:e}}function resolveLocalDevelopmentClientOptions(e){let t={host:e.serverUrl,redirect:`manual`};if(hasAuthorizationHeader(e.headers))return{...t,headers:e.headers};let n={...t,auth:{bearer:e.token}};return e.headers===void 0?n:{...n,headers:e.headers}}function resolveRemoteDevelopmentClientOptions(e){let t=new URL(e.serverUrl).origin;if(e.credentials.serverOrigin!==t)throw Error(`Credential gate origin ${e.credentials.serverOrigin} does not match client origin ${t}.`);return hasAuthorizationHeader(e.headers)?{headers:()=>resolveRemoteHeaders({credentials:e.credentials,headers:e.headers,includeTrustedOidcHeader:!0}),host:e.serverUrl,redirect:`manual`}:{auth:{vercelOidc:{token:()=>e.credentials.resolveToken()}},headers:e.headers===void 0?e.credentials.resolveBypassHeaders:()=>resolveRemoteHeaders({credentials:e.credentials,headers:e.headers,includeTrustedOidcHeader:!1}),host:e.serverUrl,redirect:`manual`}}export{resolveDevelopmentClientOptions,resolveLocalDevelopmentClientOptions,resolveRemoteDevelopmentClientOptions};
@@ -1 +1 @@
1
- import{fetchGatewayModelIds}from"../gateway-models.js";function preflight(t){let n=t.deps??{fetchGatewayModelIds};return{id:`preflight`,async gather({state:e}){return t.headless?{model:e.modelId.length>0?e.modelId:null}:{model:null}},async perform({input:e}){return e.model!==null&&await validateModel(n,e.model,t.cwd),null},apply(e){return e}}}async function validateModel(e,t,n){let r=await e.fetchGatewayModelIds(n);if(!(r===null||r.has(t)))throw Error(`Model "${t}" is not in the AI Gateway catalog. Pass a model id from https://ai-gateway.vercel.sh/v1/models (e.g. anthropic/claude-sonnet-4.6).`)}export{preflight};
1
+ import{fetchGatewayModelIds}from"../gateway-models.js";function preflight(t){let n=t.deps??{fetchGatewayModelIds};return{id:`preflight`,async gather({state:e}){return t.headless?{model:e.modelId.length>0?e.modelId:null}:{model:null}},async perform({input:e}){return e.model!==null&&await validateModel(n,e.model,t.cwd),null},apply(e){return e}}}async function validateModel(e,t,n){let r=await e.fetchGatewayModelIds(n);if(!(r===null||r.has(t)))throw Error(`Model "${t}" is not in the AI Gateway catalog. Pass a model id from https://ai-gateway.vercel.sh/v1/models (e.g. anthropic/claude-sonnet-5).`)}export{preflight};
@@ -1 +1 @@
1
- import{select}from"../ask.js";import{DEFAULT_AGENT_MODEL_ID}from"#shared/default-agent-model.js";const POPULAR_PROVIDERS=[`anthropic`,`openai`,`google`];function modelOption(e,t,n,r=!0){return{id:e,label:t,value:e,hint:n,featured:r||void 0}}const FEATURED_MODEL_IDS=[DEFAULT_AGENT_MODEL_ID,`anthropic/claude-opus-4.8`,`openai/gpt-5.5`],FALLBACK_MODELS=[modelOption(DEFAULT_AGENT_MODEL_ID,`Claude Sonnet 4.6`,`Anthropic`),modelOption(`anthropic/claude-opus-4.8`,`Claude Opus 4.8`,`Anthropic`),modelOption(`openai/gpt-5.5`,`GPT-5.5`,`OpenAI`),modelOption(`google/gemini-3.5`,`Gemini 3.5`,`Google`,!1)];function providerLabel(e){return e.length===0?``:e.charAt(0).toUpperCase()+e.slice(1)}function providerPriority(e){let t=POPULAR_PROVIDERS.indexOf(e);return t===-1?POPULAR_PROVIDERS.length:t}async function fetchGatewayCatalog(e){let t=new AbortController,n=setTimeout(()=>t.abort(),5e3);try{let n=e===void 0?t.signal:AbortSignal.any([e,t.signal]);return(await(await fetch(`https://ai-gateway.vercel.sh/v1/models`,{signal:n})).json()).data}finally{clearTimeout(n)}}function featuredPriority(e){let t=FEATURED_MODEL_IDS.indexOf(e);return t===-1?FEATURED_MODEL_IDS.length:t}async function buildModelOptions(e,t){try{let n=(await e(t)).filter(e=>e.type===`language`&&(e.tags??[]).includes(`web-search`)).map(e=>{let t=e.id.split(`/`)[0]??``;return{value:e.id,label:e.name,hint:providerLabel(t),provider:t}}).sort((e,t)=>{let n=featuredPriority(e.value)-featuredPriority(t.value);if(n!==0)return n;let r=providerPriority(e.provider)-providerPriority(t.provider);if(r!==0)return r;let i=e.provider.localeCompare(t.provider);return i===0?e.label.localeCompare(t.label):i});return n.length===0?FALLBACK_MODELS:n.map(({value:e,label:t,hint:n})=>({id:e,label:t,value:e,hint:n,featured:FEATURED_MODEL_IDS.includes(e)||void 0}))}catch{return t?.throwIfAborted(),FALLBACK_MODELS}}function selectModel(n){let r=n.deps??{fetchModels:fetchGatewayCatalog};return{id:`select-model`,async gather({signal:i}){let a=n.presetModel;if(a!==void 0&&a.length>0)return a;let o=await buildModelOptions(r.fetchModels,i),s=n.defaultModel!==void 0&&o.some(e=>e.value===n.defaultModel)?n.defaultModel:o.some(e=>e.value===DEFAULT_AGENT_MODEL_ID)?DEFAULT_AGENT_MODEL_ID:o[0]?.value;return n.asker.ask(select({key:`model`,message:`Which model should your agent use?`,options:o,recommended:s,required:!0,search:!0,placeholder:`type to search`}))},async perform({input:e}){return e},apply(e,t){return{...e,modelId:t}}}}export{fetchGatewayCatalog,selectModel};
1
+ import{select}from"../ask.js";import{DEFAULT_AGENT_MODEL_ID}from"#shared/default-agent-model.js";const POPULAR_PROVIDERS=[`anthropic`,`openai`,`google`];function modelOption(e,t,n,r=!0){return{id:e,label:t,value:e,hint:n,featured:r||void 0}}const FEATURED_MODEL_IDS=[DEFAULT_AGENT_MODEL_ID,`anthropic/claude-opus-4.8`,`openai/gpt-5.5`],FALLBACK_MODELS=[modelOption(DEFAULT_AGENT_MODEL_ID,`Claude Sonnet 5`,`Anthropic`),modelOption(`anthropic/claude-opus-4.8`,`Claude Opus 4.8`,`Anthropic`),modelOption(`openai/gpt-5.5`,`GPT-5.5`,`OpenAI`),modelOption(`google/gemini-3.5`,`Gemini 3.5`,`Google`,!1)];function providerLabel(e){return e.length===0?``:e.charAt(0).toUpperCase()+e.slice(1)}function providerPriority(e){let t=POPULAR_PROVIDERS.indexOf(e);return t===-1?POPULAR_PROVIDERS.length:t}async function fetchGatewayCatalog(e){let t=new AbortController,n=setTimeout(()=>t.abort(),5e3);try{let n=e===void 0?t.signal:AbortSignal.any([e,t.signal]);return(await(await fetch(`https://ai-gateway.vercel.sh/v1/models`,{signal:n})).json()).data}finally{clearTimeout(n)}}function featuredPriority(e){let t=FEATURED_MODEL_IDS.indexOf(e);return t===-1?FEATURED_MODEL_IDS.length:t}async function buildModelOptions(e,t){try{let n=(await e(t)).filter(e=>e.type===`language`&&(e.tags??[]).includes(`web-search`)).map(e=>{let t=e.id.split(`/`)[0]??``;return{value:e.id,label:e.name,hint:providerLabel(t),provider:t}}).sort((e,t)=>{let n=featuredPriority(e.value)-featuredPriority(t.value);if(n!==0)return n;let r=providerPriority(e.provider)-providerPriority(t.provider);if(r!==0)return r;let i=e.provider.localeCompare(t.provider);return i===0?e.label.localeCompare(t.label):i});return n.length===0?FALLBACK_MODELS:n.map(({value:e,label:t,hint:n})=>({id:e,label:t,value:e,hint:n,featured:FEATURED_MODEL_IDS.includes(e)||void 0}))}catch{return t?.throwIfAborted(),FALLBACK_MODELS}}function selectModel(n){let r=n.deps??{fetchModels:fetchGatewayCatalog};return{id:`select-model`,async gather({signal:i}){let a=n.presetModel;if(a!==void 0&&a.length>0)return a;let o=await buildModelOptions(r.fetchModels,i),s=n.defaultModel!==void 0&&o.some(e=>e.value===n.defaultModel)?n.defaultModel:o.some(e=>e.value===DEFAULT_AGENT_MODEL_ID)?DEFAULT_AGENT_MODEL_ID:o[0]?.value;return n.asker.ask(select({key:`model`,message:`Which model should your agent use?`,options:o,recommended:s,required:!0,search:!0,placeholder:`type to search`}))},async perform({input:e}){return e},apply(e,t){return{...e,modelId:t}}}}export{fetchGatewayCatalog,selectModel};
@@ -1 +1 @@
1
- import{__toESM}from"../../_virtual/_rolldown/runtime.js";import{require_picocolors}from"../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js";import{AI_GATEWAY_API_KEY_ENV_VAR}from"../ai-gateway-api-key.js";import{interactiveAsker}from"../ask.js";import{detectProjectIdentity}from"../project-resolution.js";import{snapshotSetupState}from"../state.js";import{WizardCancelledError}from"../step.js";import{withSpinner}from"../with-spinner.js";import{findEnvFileWithKey}from"../boxes/detect-ai-gateway.js";import{fetchGatewayCatalog,selectModel}from"../boxes/select-model.js";import{runInteractive}from"../runner.js";import{inProjectSetupState,prompterSink}from"./in-project.js";import{runProviderFlow}from"./provider.js";import{join}from"node:path";import{discoverAgent}from"#discover/discover-agent.js";import{inspectApplication}from"#services/inspect-application.js";import{formatLanguageModelGatewayId}from"#internal/runtime-model.js";import{createCompiledRuntimeModelCatalogLoader}from"#compiler/model-catalog.js";import{createStaticSourceChange}from"#source-change/static-source-change.js";var import_picocolors=__toESM(require_picocolors(),1);const MODEL_MENU_MESSAGE=``;function providerStatusHint(e,t=e=>e){return e.kind===`gateway-project`?`AI Gateway (Linked to ${e.teamName===void 0?t(e.projectName):`${t(e.projectName)} in ${t(e.teamName)}`})`:`AI Gateway (${e.envKey} in ${e.envFile})`}function modelMenuRows(e,t,n,r){let i;r?(i={value:`model`,label:`Change model`,description:`The model your agent uses`},e!==null&&(i.hint=e)):i={value:`model`,label:`Change model`,disabled:!0,description:`Set via an SDK model call in agent.ts; edit the source to change it`};let a;return a=n?.kind===`external`?{disabled:!0,value:`provider`,label:`Change provider`,description:`Disabled in external endpoint mode`}:t.kind===`unset`?{value:`provider`,label:import_picocolors.default.bold(`Configure model access`),hint:import_picocolors.default.yellow(`Not configured`),description:`How your agent reaches the model provider`,accent:`warning`}:{value:`provider`,label:`Change provider`,hint:providerStatusHint(t,import_picocolors.default.bold),description:`How your agent reaches the model provider`},[i,a,{value:`done`,label:`Done`,description:`Return to the prompt`}]}async function detectModelProviderStatus(e,t={}){let[r,a,o]=await Promise.all([detectProjectIdentity(e,t),findEnvFileWithKey(e,AI_GATEWAY_API_KEY_ENV_VAR),findEnvFileWithKey(e,`VERCEL_OIDC_TOKEN`)]);if(r!==void 0){let e={kind:`gateway-project`,projectName:r.projectName};return r.teamName!==void 0&&(e.teamName=r.teamName),e}return a===void 0?o===void 0?{kind:`unset`}:{kind:`gateway-key`,envKey:`VERCEL_OIDC_TOKEN`,envFile:o}:{kind:`gateway-key`,envKey:AI_GATEWAY_API_KEY_ENV_VAR,envFile:a}}async function runModelFlow(e){let{appRoot:t,prompter:n,signal:r}=e,i={readCurrentModel:readCurrentAgentModel,applyModel:changeAgentModel,detectProviderStatus:detectModelProviderStatus,runProviderFlow,...e.deps},detectProvider=(e=!0)=>i.detectProviderStatus(t,e&&r!==void 0?{signal:r}:{}),[{id:a,routing:s,editable:c},l]=await withSpinner(n,`Checking the project…`,()=>Promise.all([i.readCurrentModel(t),detectProvider()]));r?.throwIfAborted();let u,d,f=s?.kind===`external`?{tone:`warning`,text:"`agent.ts` specifies a model provider directly. In-TUI configuration is restricted to AI Gateway endpoints."}:void 0,m=l.kind===`unset`&&s?.kind!==`external`?`provider`:c?`model`:s?.kind===`external`?`done`:`provider`,h=s?.kind!==`external`&&(e.initialStep===`provider`||l.kind===`unset`);for(;;){let e;if(h)h=!1,e=`provider`;else try{e=await n.select({message:``,options:modelMenuRows(a,l,s,c),hintLayout:`stacked`,initialValue:m,notices:f===void 0?[]:[f]})}catch(e){if(!(e instanceof WizardCancelledError))throw e;break}if(e===`done`)break;if(e===`model`){let e=await pickModelFromCatalog({appRoot:t,prompter:n,current:a,signal:r,deps:i.selectModel});if(e===void 0){m=`model`;continue}r?.throwIfAborted(),u=await i.applyModel({appRoot:t,slug:e}),r?.throwIfAborted();break}let p=await i.runProviderFlow({appRoot:t,prompter:n,signal:r});if(p.kind===`cancelled`){if(r?.aborted)return{kind:`cancelled`};m=`provider`;continue}if(p.kind===`external-provider`){if(r?.aborted)return{kind:`cancelled`};m=`done`;continue}l=await withSpinner(n,`Checking the project…`,()=>detectProvider(!1)),d={status:l},p.credential!==void 0&&(d.credential=p.credential);break}if(u===void 0&&d===void 0)return{kind:`cancelled`};let g={kind:`done`};return u!==void 0&&(g.modelMessage=formatApplyModelOutcome(u)),d!==void 0&&(g.providerOutcome=d),g}async function pickModelFromCatalog(e){let{appRoot:t,prompter:n,current:i,signal:o}=e,s=e.deps?.fetchModels??fetchGatewayCatalog,p={asker:interactiveAsker(n),deps:{fetchModels:e=>withSpinner(n,`Loading the model catalog...`,()=>s(e))}};i!==null&&(p.defaultModel=i);let m=await runInteractive([selectModel(p)],inProjectSetupState(t,{kind:`unresolved`}),prompterSink(n),{snapshot:snapshotSetupState,signal:o});return m.kind===`cancelled`?void 0:m.state.modelId}function formatApplyModelOutcome(e){switch(e.kind){case`changed`:return`Model changed to ${import_picocolors.default.bold(e.to)}. Live on your next prompt.`;case`unchanged`:return`Model is already \`${e.model}\`.`;case`rejected`:return e.message}}async function changeAgentModel(e){let{appRoot:t,slug:n}=e,r=await validateModelSlug(t,n);if(r!==null)return{kind:`rejected`,message:r};let{manifest:i}=await discoverAgent({agentRoot:join(t,`agent`),appRoot:t}),a=await createStaticSourceChange(i).updateModelName(n);return a.kind===`bail`?{kind:`rejected`,message:`Couldn't edit ${a.at.logicalPath}: ${a.reason}. Change \`model\` by hand.`}:a.from===a.to?{kind:`unchanged`,model:a.to}:{kind:`changed`,to:a.to}}async function validateModelSlug(e,t){if(!t.includes(`/`))return`\`${t}\` isn't a provider/model id (e.g. anthropic/claude-sonnet-4.6).`;let n=createCompiledRuntimeModelCatalogLoader(e);try{if(await n.getModelLimits(formatLanguageModelGatewayId(t))===null)return`I couldn't confirm \`${t}\` in the AI Gateway model catalog, so I didn't change agent.ts.`}catch{return null}return null}async function readCurrentAgentModel(e){try{let{compiledState:t}=await inspectApplication(e),n=t?.manifest.config.model;return{id:n?.id??null,routing:n?.routing??null,editable:n!==void 0&&n.source===void 0}}catch{return{id:null,routing:null,editable:!1}}}async function modelChangeRefusalForUneditableModel(e){let{editable:t,routing:n}=await readCurrentAgentModel(e);return t?null:`Model is set via ${n?.kind===`external`?`the external provider \`${n.provider}\``:`an SDK model call`} in agent.ts, not a string literal; /model can't rewrite it. Edit \`model\` in agent.ts.`}export{MODEL_MENU_MESSAGE,changeAgentModel,detectModelProviderStatus,formatApplyModelOutcome,modelChangeRefusalForUneditableModel,runModelFlow};
1
+ import{__toESM}from"../../_virtual/_rolldown/runtime.js";import{require_picocolors}from"../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js";import{AI_GATEWAY_API_KEY_ENV_VAR}from"../ai-gateway-api-key.js";import{interactiveAsker}from"../ask.js";import{detectProjectIdentity}from"../project-resolution.js";import{snapshotSetupState}from"../state.js";import{WizardCancelledError}from"../step.js";import{withSpinner}from"../with-spinner.js";import{findEnvFileWithKey}from"../boxes/detect-ai-gateway.js";import{fetchGatewayCatalog,selectModel}from"../boxes/select-model.js";import{runInteractive}from"../runner.js";import{inProjectSetupState,prompterSink}from"./in-project.js";import{runProviderFlow}from"./provider.js";import{join}from"node:path";import{discoverAgent}from"#discover/discover-agent.js";import{inspectApplication}from"#services/inspect-application.js";import{formatLanguageModelGatewayId}from"#internal/runtime-model.js";import{createCompiledRuntimeModelCatalogLoader}from"#compiler/model-catalog.js";import{createStaticSourceChange}from"#source-change/static-source-change.js";var import_picocolors=__toESM(require_picocolors(),1);const MODEL_MENU_MESSAGE=``;function providerStatusHint(e,t=e=>e){return e.kind===`gateway-project`?`AI Gateway (Linked to ${e.teamName===void 0?t(e.projectName):`${t(e.projectName)} in ${t(e.teamName)}`})`:`AI Gateway (${e.envKey} in ${e.envFile})`}function modelMenuRows(e,t,n,r){let i;r?(i={value:`model`,label:`Change model`,description:`The model your agent uses`},e!==null&&(i.hint=e)):i={value:`model`,label:`Change model`,disabled:!0,description:`Set via an SDK model call in agent.ts; edit the source to change it`};let a;return a=n?.kind===`external`?{disabled:!0,value:`provider`,label:`Change provider`,description:`Disabled in external endpoint mode`}:t.kind===`unset`?{value:`provider`,label:import_picocolors.default.bold(`Configure model access`),hint:import_picocolors.default.yellow(`Not configured`),description:`How your agent reaches the model provider`,accent:`warning`}:{value:`provider`,label:`Change provider`,hint:providerStatusHint(t,import_picocolors.default.bold),description:`How your agent reaches the model provider`},[i,a,{value:`done`,label:`Done`,description:`Return to the prompt`}]}async function detectModelProviderStatus(e,t={}){let[r,a,o]=await Promise.all([detectProjectIdentity(e,t),findEnvFileWithKey(e,AI_GATEWAY_API_KEY_ENV_VAR),findEnvFileWithKey(e,`VERCEL_OIDC_TOKEN`)]);if(r!==void 0){let e={kind:`gateway-project`,projectName:r.projectName};return r.teamName!==void 0&&(e.teamName=r.teamName),e}return a===void 0?o===void 0?{kind:`unset`}:{kind:`gateway-key`,envKey:`VERCEL_OIDC_TOKEN`,envFile:o}:{kind:`gateway-key`,envKey:AI_GATEWAY_API_KEY_ENV_VAR,envFile:a}}async function runModelFlow(e){let{appRoot:t,prompter:n,signal:r}=e,i={readCurrentModel:readCurrentAgentModel,applyModel:changeAgentModel,detectProviderStatus:detectModelProviderStatus,runProviderFlow,...e.deps},detectProvider=(e=!0)=>i.detectProviderStatus(t,e&&r!==void 0?{signal:r}:{}),[{id:a,routing:s,editable:c},l]=await withSpinner(n,`Checking the project…`,()=>Promise.all([i.readCurrentModel(t),detectProvider()]));r?.throwIfAborted();let u,d,f=s?.kind===`external`?{tone:`warning`,text:"`agent.ts` specifies a model provider directly. In-TUI configuration is restricted to AI Gateway endpoints."}:void 0,m=l.kind===`unset`&&s?.kind!==`external`?`provider`:c?`model`:s?.kind===`external`?`done`:`provider`,h=s?.kind!==`external`&&(e.initialStep===`provider`||l.kind===`unset`);for(;;){let e;if(h)h=!1,e=`provider`;else try{e=await n.select({message:``,options:modelMenuRows(a,l,s,c),hintLayout:`stacked`,initialValue:m,notices:f===void 0?[]:[f]})}catch(e){if(!(e instanceof WizardCancelledError))throw e;break}if(e===`done`)break;if(e===`model`){let e=await pickModelFromCatalog({appRoot:t,prompter:n,current:a,signal:r,deps:i.selectModel});if(e===void 0){m=`model`;continue}r?.throwIfAborted(),u=await i.applyModel({appRoot:t,slug:e}),r?.throwIfAborted();break}let p=await i.runProviderFlow({appRoot:t,prompter:n,signal:r});if(p.kind===`cancelled`){if(r?.aborted)return{kind:`cancelled`};m=`provider`;continue}if(p.kind===`external-provider`){if(r?.aborted)return{kind:`cancelled`};m=`done`;continue}l=await withSpinner(n,`Checking the project…`,()=>detectProvider(!1)),d={status:l},p.credential!==void 0&&(d.credential=p.credential);break}if(u===void 0&&d===void 0)return{kind:`cancelled`};let g={kind:`done`};return u!==void 0&&(g.modelMessage=formatApplyModelOutcome(u)),d!==void 0&&(g.providerOutcome=d),g}async function pickModelFromCatalog(e){let{appRoot:t,prompter:n,current:i,signal:o}=e,s=e.deps?.fetchModels??fetchGatewayCatalog,p={asker:interactiveAsker(n),deps:{fetchModels:e=>withSpinner(n,`Loading the model catalog...`,()=>s(e))}};i!==null&&(p.defaultModel=i);let m=await runInteractive([selectModel(p)],inProjectSetupState(t,{kind:`unresolved`}),prompterSink(n),{snapshot:snapshotSetupState,signal:o});return m.kind===`cancelled`?void 0:m.state.modelId}function formatApplyModelOutcome(e){switch(e.kind){case`changed`:return`Model changed to ${import_picocolors.default.bold(e.to)}. Live on your next prompt.`;case`unchanged`:return`Model is already \`${e.model}\`.`;case`rejected`:return e.message}}async function changeAgentModel(e){let{appRoot:t,slug:n}=e,r=await validateModelSlug(t,n);if(r!==null)return{kind:`rejected`,message:r};let{manifest:i}=await discoverAgent({agentRoot:join(t,`agent`),appRoot:t}),a=await createStaticSourceChange(i).updateModelName(n);return a.kind===`bail`?{kind:`rejected`,message:`Couldn't edit ${a.at.logicalPath}: ${a.reason}. Change \`model\` by hand.`}:a.from===a.to?{kind:`unchanged`,model:a.to}:{kind:`changed`,to:a.to}}async function validateModelSlug(e,t){if(!t.includes(`/`))return`\`${t}\` isn't a provider/model id (e.g. anthropic/claude-sonnet-5).`;let n=createCompiledRuntimeModelCatalogLoader(e);try{if(await n.getModelLimits(formatLanguageModelGatewayId(t))===null)return`I couldn't confirm \`${t}\` in the AI Gateway model catalog, so I didn't change agent.ts.`}catch{return null}return null}async function readCurrentAgentModel(e){try{let{compiledState:t}=await inspectApplication(e),n=t?.manifest.config.model;return{id:n?.id??null,routing:n?.routing??null,editable:n!==void 0&&n.source===void 0}}catch{return{id:null,routing:null,editable:!1}}}async function modelChangeRefusalForUneditableModel(e){let{editable:t,routing:n}=await readCurrentAgentModel(e);return t?null:`Model is set via ${n?.kind===`external`?`the external provider \`${n.provider}\``:`an SDK model call`} in agent.ts, not a string literal; /model can't rewrite it. Edit \`model\` in agent.ts.`}export{MODEL_MENU_MESSAGE,changeAgentModel,detectModelProviderStatus,formatApplyModelOutcome,modelChangeRefusalForUneditableModel,runModelFlow};
@@ -21,7 +21,7 @@ export declare const DEFAULT_EVE_PACKAGE_CONTRACT: EvePackageContract;
21
21
  export declare function resolveEvePackageContract(contract?: EvePackageContract): EvePackageContract;
22
22
  /**
23
23
  * Provider slug a gateway model id routes through: the segment before the
24
- * first "/" (e.g. `anthropic/claude-sonnet-4.6` → `anthropic`). The slug is
24
+ * first "/" (e.g. `anthropic/claude-sonnet-5` → `anthropic`). The slug is
25
25
  * injected into generated source, so characters outside the catalog's slug
26
26
  * alphabet are dropped; an id without a usable prefix falls back to
27
27
  * `anthropic`.