experimental-ash 0.7.6 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +79 -56
- package/dist/docs/public/channels/README.md +33 -23
- package/dist/docs/public/channels/attachments.md +42 -29
- package/dist/src/channel/adapter.d.ts +21 -28
- package/dist/src/channel/compiled-channel.d.ts +1 -1
- package/dist/src/channel/http.d.ts +29 -0
- package/dist/src/channel/http.js +30 -0
- package/dist/src/channel/schedule.d.ts +20 -0
- package/dist/src/channel/schedule.js +22 -1
- package/dist/src/channel/send.d.ts +1 -1
- package/dist/src/channel/send.js +22 -1
- package/dist/src/channel/types.d.ts +1 -1
- package/dist/src/chunks/{client-DBMG7iuf.js → client-BeZ_W7vl.js} +2 -2
- package/dist/src/chunks/{dev-authored-source-watcher-BcN7BUDE.js → dev-authored-source-watcher-BFC_yNcP.js} +1 -1
- package/dist/src/chunks/host-DMccRKcz.js +22 -0
- package/dist/src/chunks/{paths-BYIdCNw9.js → paths-B-aiDznc.js} +26 -26
- package/dist/src/chunks/{prewarm-DXhyk7i9.js → prewarm-CCbReSNm.js} +1 -1
- package/dist/src/chunks/types-MZUhN0Zy.js +1 -0
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/dev/repl.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +1 -1
- package/dist/src/compiled/@vercel/sandbox/index.d.ts +37 -3
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/execution/sandbox/bindings/local.d.ts +0 -2
- package/dist/src/execution/sandbox/bindings/local.js +1 -20
- package/dist/src/execution/sandbox/bindings/vercel.d.ts +2 -2
- package/dist/src/execution/sandbox/bindings/vercel.js +1 -12
- package/dist/src/harness/attachment-staging.js +54 -50
- package/dist/src/harness/emission.d.ts +14 -1
- package/dist/src/harness/emission.js +15 -2
- package/dist/src/harness/tool-loop.js +28 -2
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/attachments/url-refs.d.ts +14 -0
- package/dist/src/internal/attachments/url-refs.js +20 -0
- package/dist/src/internal/nitro/host/configure-nitro-routes.d.ts +0 -1
- package/dist/src/internal/nitro/host/configure-nitro-routes.js +24 -17
- package/dist/src/internal/nitro/host/create-application-nitro.js +1 -16
- package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response.d.ts +87 -0
- package/dist/src/internal/nitro/routes/{home-page/build-home-page-response.js → agent-info/build-agent-info-response.js} +6 -6
- package/dist/src/internal/nitro/routes/{home-page/load-home-page-data.d.ts → agent-info/load-agent-info-data.d.ts} +8 -8
- package/dist/src/internal/nitro/routes/{home-page/load-home-page-data.js → agent-info/load-agent-info-data.js} +7 -8
- package/dist/src/internal/nitro/routes/index.d.ts +10 -5
- package/dist/src/internal/nitro/routes/index.js +225 -18
- package/dist/src/internal/nitro/routes/info.d.ts +14 -0
- package/dist/src/internal/nitro/routes/info.js +50 -0
- package/dist/src/protocol/routes.d.ts +8 -6
- package/dist/src/protocol/routes.js +8 -6
- package/dist/src/public/channels/ash.d.ts +8 -1
- package/dist/src/public/channels/ash.js +1 -6
- package/dist/src/public/channels/index.d.ts +1 -1
- package/dist/src/public/channels/slack/attachments.d.ts +14 -18
- package/dist/src/public/channels/slack/attachments.js +30 -36
- package/dist/src/public/channels/slack/index.d.ts +1 -2
- package/dist/src/public/channels/slack/slackChannel.d.ts +12 -1
- package/dist/src/public/channels/slack/slackChannel.js +34 -48
- package/dist/src/public/definitions/defineChannel.d.ts +9 -7
- package/dist/src/public/definitions/defineChannel.js +5 -11
- package/dist/src/public/definitions/sandbox.d.ts +3 -3
- package/dist/src/public/sandbox/backends/vercel.d.ts +2 -2
- package/dist/src/public/sandbox/index.d.ts +2 -2
- package/dist/src/public/sandbox/vercel-sandbox.d.ts +11 -10
- package/dist/src/runtime/channels/registry.js +9 -3
- package/dist/src/runtime/resolve-channel.js +2 -1
- package/dist/src/shared/sandbox-backend.d.ts +4 -4
- package/dist/src/shared/sandbox-definition.d.ts +6 -36
- package/package.json +1 -1
- package/dist/src/chunks/host-33-Sb6vq.js +0 -22
- package/dist/src/chunks/types-D9Uv7nU4.js +0 -1
- package/dist/src/internal/nitro/host/load-home-page-web-assets.d.ts +0 -12
- package/dist/src/internal/nitro/host/load-home-page-web-assets.js +0 -34
- package/dist/src/internal/nitro/routes/home-page/build-home-page-response.d.ts +0 -87
- package/dist/src/internal/nitro/routes/home.d.ts +0 -6
- package/dist/src/internal/nitro/routes/home.js +0 -21
- package/dist/src/internal/nitro/routes/web-ui/assets/index-BQa8fbHJ.js +0 -11
- package/dist/src/internal/nitro/routes/web-ui/assets/style-Kqb6YxTP.css +0 -2
- package/dist/src/internal/nitro/routes/web-ui/index.html +0 -17
- package/dist/src/public/sandboxes/vercel-sandbox.d.ts +0 -41
- package/dist/src/public/sandboxes/vercel-sandbox.js +0 -1
|
@@ -8,9 +8,15 @@ export declare const ASH_ROUTE_PREFIX = "/ash/v1";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const ASH_HEALTH_ROUTE_PATH = "/ash/v1/health";
|
|
10
10
|
/**
|
|
11
|
-
* Stable framework-owned route exposing
|
|
11
|
+
* Stable framework-owned route exposing the JSON inspection payload for
|
|
12
|
+
* the current agent (model id, instructions preview, skills, schedules,
|
|
13
|
+
* subagents, tools, sandbox, and the runtime's own endpoint catalog).
|
|
14
|
+
*
|
|
15
|
+
* Default auth on this route is `vercelOidc()` on Vercel deployments
|
|
16
|
+
* (with the same-project bypass for in-deployment callers) and `none()`
|
|
17
|
+
* off Vercel. See `routes/info.ts` for the handler.
|
|
12
18
|
*/
|
|
13
|
-
export declare const
|
|
19
|
+
export declare const ASH_INFO_ROUTE_PATH = "/ash/v1/info";
|
|
14
20
|
/**
|
|
15
21
|
* Stable framework-owned route for creating a new session.
|
|
16
22
|
*/
|
|
@@ -24,10 +30,6 @@ export declare const ASH_CONTINUE_SESSION_ROUTE_PATTERN = "/ash/v1/session/:sess
|
|
|
24
30
|
* Stable framework-owned message stream route pattern.
|
|
25
31
|
*/
|
|
26
32
|
export declare const ASH_MESSAGE_STREAM_ROUTE_PATTERN = "/ash/v1/session/:sessionId/stream";
|
|
27
|
-
/**
|
|
28
|
-
* Stable framework-owned route prefix for static home-page UI assets.
|
|
29
|
-
*/
|
|
30
|
-
export declare const ASH_HOME_PAGE_UI_ROUTE_PREFIX = "/ash/v1/ui";
|
|
31
33
|
/**
|
|
32
34
|
* Stable framework-owned route pattern for receiving inbound IdP redirects
|
|
33
35
|
* during in-turn interactive connection authorization.
|
|
@@ -8,9 +8,15 @@ export const ASH_ROUTE_PREFIX = "/ash/v1";
|
|
|
8
8
|
*/
|
|
9
9
|
export const ASH_HEALTH_ROUTE_PATH = `${ASH_ROUTE_PREFIX}/health`;
|
|
10
10
|
/**
|
|
11
|
-
* Stable framework-owned route exposing
|
|
11
|
+
* Stable framework-owned route exposing the JSON inspection payload for
|
|
12
|
+
* the current agent (model id, instructions preview, skills, schedules,
|
|
13
|
+
* subagents, tools, sandbox, and the runtime's own endpoint catalog).
|
|
14
|
+
*
|
|
15
|
+
* Default auth on this route is `vercelOidc()` on Vercel deployments
|
|
16
|
+
* (with the same-project bypass for in-deployment callers) and `none()`
|
|
17
|
+
* off Vercel. See `routes/info.ts` for the handler.
|
|
12
18
|
*/
|
|
13
|
-
export const
|
|
19
|
+
export const ASH_INFO_ROUTE_PATH = `${ASH_ROUTE_PREFIX}/info`;
|
|
14
20
|
/**
|
|
15
21
|
* Stable framework-owned route for creating a new session.
|
|
16
22
|
*/
|
|
@@ -24,10 +30,6 @@ export const ASH_CONTINUE_SESSION_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/session/:
|
|
|
24
30
|
* Stable framework-owned message stream route pattern.
|
|
25
31
|
*/
|
|
26
32
|
export const ASH_MESSAGE_STREAM_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/session/:sessionId/stream`;
|
|
27
|
-
/**
|
|
28
|
-
* Stable framework-owned route prefix for static home-page UI assets.
|
|
29
|
-
*/
|
|
30
|
-
export const ASH_HOME_PAGE_UI_ROUTE_PREFIX = `${ASH_ROUTE_PREFIX}/ui`;
|
|
31
33
|
/**
|
|
32
34
|
* Stable framework-owned route pattern for receiving inbound IdP redirects
|
|
33
35
|
* during in-turn interactive connection authorization.
|
|
@@ -5,4 +5,11 @@ export interface AshChannelInput {
|
|
|
5
5
|
readonly auth: AuthFn<Request>;
|
|
6
6
|
readonly uploadPolicy?: Partial<UploadPolicy>;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Concrete return type of {@link ashChannel}. Named so consumers can
|
|
10
|
+
* default-export an `ashChannel(...)` call under `declaration: true`
|
|
11
|
+
* without TypeScript falling back to an internal path for `Channel`.
|
|
12
|
+
*/
|
|
13
|
+
export interface AshChannel extends Channel {
|
|
14
|
+
}
|
|
15
|
+
export declare function ashChannel(input: AshChannelInput): AshChannel;
|
|
@@ -29,12 +29,7 @@ export function ashChannel(input) {
|
|
|
29
29
|
if (policyRejection !== null)
|
|
30
30
|
return policyRejection;
|
|
31
31
|
const token = `ash:${crypto.randomUUID()}`;
|
|
32
|
-
const
|
|
33
|
-
? body.message
|
|
34
|
-
: body.message
|
|
35
|
-
? JSON.stringify(body.message)
|
|
36
|
-
: "";
|
|
37
|
-
const session = await send(messageText, {
|
|
32
|
+
const session = await send(body.message, {
|
|
38
33
|
auth: sessionAuth,
|
|
39
34
|
continuationToken: token,
|
|
40
35
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { defineChannel, POST, GET, PUT, DELETE, type
|
|
1
|
+
export { defineChannel, POST, GET, PUT, DELETE, type Channel, type ChannelConfig, type ChannelEvents, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import type { FilePart, UserContent } from "ai";
|
|
2
2
|
import type { SlackBotToken } from "#compiled/@chat-adapter/slack/index.js";
|
|
3
3
|
import type { Message } from "#compiled/chat/index.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { FetchFileResult } from "#channel/adapter.js";
|
|
5
5
|
import type { UploadPolicy } from "#public/channels/upload-policy.js";
|
|
6
|
-
/**
|
|
7
|
-
* Payload an `ash-attachment:` ref carries for Slack file uploads.
|
|
8
|
-
* The Slack resolver reads `params.url` and fetches it with the bot
|
|
9
|
-
* token.
|
|
10
|
-
*/
|
|
11
|
-
export interface SlackAttachmentParams {
|
|
12
|
-
readonly url: string;
|
|
13
|
-
}
|
|
14
6
|
/**
|
|
15
7
|
* Emits one {@link FilePart} per supported attachment in the inbound
|
|
16
|
-
* message, with `data` set to
|
|
17
|
-
* video,
|
|
18
|
-
* single bad upload never blocks the text portion
|
|
8
|
+
* message, with `data` set to a `URL` object pointing at the Slack
|
|
9
|
+
* file. Audio, video, URL-less, and policy-violating attachments are
|
|
10
|
+
* dropped so a single bad upload never blocks the text portion.
|
|
11
|
+
*
|
|
12
|
+
* The `URL` object in `data` is resolved by the channel's `fetchFile`
|
|
13
|
+
* function at staging time inside the workflow step.
|
|
19
14
|
*/
|
|
20
15
|
export declare function collectSlackFileParts(message: Pick<Message, "attachments">, policy: UploadPolicy): FilePart[];
|
|
21
16
|
/**
|
|
@@ -25,11 +20,12 @@ export declare function collectSlackFileParts(message: Pick<Message, "attachment
|
|
|
25
20
|
*/
|
|
26
21
|
export declare function buildSlackTurnMessage(text: string, fileParts: readonly FilePart[]): string | UserContent;
|
|
27
22
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
23
|
+
* Creates a `fetchFile` function for the Slack channel.
|
|
24
|
+
*
|
|
25
|
+
* Returns `null` for URLs that don't belong to Slack so they pass
|
|
26
|
+
* through to the model provider unchanged. Fetches Slack file URLs
|
|
27
|
+
* with the bot token.
|
|
32
28
|
*/
|
|
33
|
-
export declare function
|
|
29
|
+
export declare function createSlackFetchFile(input: {
|
|
34
30
|
readonly botToken?: SlackBotToken;
|
|
35
|
-
}):
|
|
31
|
+
}): (url: string) => Promise<FetchFileResult | null>;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { AshAttachmentError } from "#internal/attachments/errors.js";
|
|
2
|
-
import { encodeAttachmentRef } from "#internal/attachments/refs.js";
|
|
3
1
|
import { createLogger } from "#internal/logging.js";
|
|
4
2
|
import { evaluateFilePart, formatUploadPolicyViolation } from "#public/channels/upload-policy.js";
|
|
5
3
|
const log = createLogger("slack.attachments");
|
|
6
4
|
/**
|
|
7
5
|
* Emits one {@link FilePart} per supported attachment in the inbound
|
|
8
|
-
* message, with `data` set to
|
|
9
|
-
* video,
|
|
10
|
-
* single bad upload never blocks the text portion
|
|
6
|
+
* message, with `data` set to a `URL` object pointing at the Slack
|
|
7
|
+
* file. Audio, video, URL-less, and policy-violating attachments are
|
|
8
|
+
* dropped so a single bad upload never blocks the text portion.
|
|
9
|
+
*
|
|
10
|
+
* The `URL` object in `data` is resolved by the channel's `fetchFile`
|
|
11
|
+
* function at staging time inside the workflow step.
|
|
11
12
|
*/
|
|
12
13
|
export function collectSlackFileParts(message, policy) {
|
|
13
14
|
const parts = [];
|
|
@@ -31,7 +32,7 @@ function toSlackFilePart(attachment, index) {
|
|
|
31
32
|
return null;
|
|
32
33
|
}
|
|
33
34
|
if (!attachment.url) {
|
|
34
|
-
log.warn("dropped attachment — no url available
|
|
35
|
+
log.warn("dropped attachment — no url available", {
|
|
35
36
|
name: attachment.name,
|
|
36
37
|
});
|
|
37
38
|
return null;
|
|
@@ -40,10 +41,7 @@ function toSlackFilePart(attachment, index) {
|
|
|
40
41
|
type: "file",
|
|
41
42
|
mediaType: attachment.mimeType ?? "application/octet-stream",
|
|
42
43
|
filename: attachment.name ?? `attachment-${index}`,
|
|
43
|
-
data:
|
|
44
|
-
params: { url: attachment.url },
|
|
45
|
-
size: attachment.size,
|
|
46
|
-
}),
|
|
44
|
+
data: new URL(attachment.url),
|
|
47
45
|
};
|
|
48
46
|
}
|
|
49
47
|
/**
|
|
@@ -62,39 +60,35 @@ export function buildSlackTurnMessage(text, fileParts) {
|
|
|
62
60
|
const textPart = { type: "text", text };
|
|
63
61
|
return [textPart, ...fileParts];
|
|
64
62
|
}
|
|
65
|
-
/** Materializes a {@link SlackBotToken} to a string at fetch time
|
|
66
|
-
* so callers can rotate (function-shaped credentials) or read lazily. */
|
|
67
63
|
async function resolveSlackBotToken(token) {
|
|
68
64
|
const source = token ?? process.env.SLACK_BOT_TOKEN;
|
|
69
65
|
if (!source) {
|
|
70
|
-
throw new Error("SLACK_BOT_TOKEN is required to
|
|
66
|
+
throw new Error("SLACK_BOT_TOKEN is required to fetch Slack files.");
|
|
71
67
|
}
|
|
72
68
|
return typeof source === "function" ? await source() : source;
|
|
73
69
|
}
|
|
74
70
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
71
|
+
* Creates a `fetchFile` function for the Slack channel.
|
|
72
|
+
*
|
|
73
|
+
* Returns `null` for URLs that don't belong to Slack so they pass
|
|
74
|
+
* through to the model provider unchanged. Fetches Slack file URLs
|
|
75
|
+
* with the bot token.
|
|
79
76
|
*/
|
|
80
|
-
export function
|
|
81
|
-
return {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
mediaType: response.headers.get("content-type") ?? undefined,
|
|
97
|
-
};
|
|
98
|
-
},
|
|
77
|
+
export function createSlackFetchFile(input) {
|
|
78
|
+
return async (url) => {
|
|
79
|
+
if (!url.startsWith("https://files.slack.com/")) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const token = await resolveSlackBotToken(input.botToken);
|
|
83
|
+
const response = await fetch(url, {
|
|
84
|
+
headers: { authorization: `Bearer ${token}` },
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`Slack file fetch returned HTTP ${response.status} for ${url}.`);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
bytes: Buffer.from(await response.arrayBuffer()),
|
|
91
|
+
mediaType: response.headers.get("content-type") ?? undefined,
|
|
92
|
+
};
|
|
99
93
|
};
|
|
100
94
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { slack, type SlackOptions } from "#public/channels/slack/slack.js";
|
|
2
|
-
export { slackChannel, type SlackApiHandle, type SlackApiResponse, type SlackChannelConfig, type SlackChannelEvents, type SlackChannelCredentials, type SlackChannelState, type SlackContext, type SlackInteractionAction, type SlackReceiveArgs, } from "#public/channels/slack/slackChannel.js";
|
|
3
|
-
export type { SlackAttachmentParams } from "#public/channels/slack/attachments.js";
|
|
2
|
+
export { slackChannel, type SlackApiHandle, type SlackApiResponse, type SlackChannel, type SlackChannelConfig, type SlackChannelEvents, type SlackChannelCredentials, type SlackChannelState, type SlackContext, type SlackInteractionAction, type SlackReceiveArgs, } from "#public/channels/slack/slackChannel.js";
|
|
4
3
|
export { Actions, Button, Card, CardText, Divider, Fields, Image, LinkButton, Modal, RadioSelect, Section, Select, SelectOption, Table, TextInput, } from "#compiled/chat/index.js";
|
|
5
4
|
export type { AdapterPostableMessage, Attachment, Author, CardElement, FileUpload, Message, PostableMessage, SentMessage, Thread, } from "#compiled/chat/index.js";
|
|
@@ -72,6 +72,10 @@ export interface SlackChannelEvents {
|
|
|
72
72
|
export interface SlackChannelConfig {
|
|
73
73
|
readonly credentials?: SlackChannelCredentials;
|
|
74
74
|
readonly botName?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Override the default webhook route path (`/ash/v1/slack`).
|
|
77
|
+
*/
|
|
78
|
+
readonly route?: string;
|
|
75
79
|
/**
|
|
76
80
|
* Inbound upload policy applied to file attachments before they
|
|
77
81
|
* reach the harness. Violating attachments are dropped with a
|
|
@@ -83,5 +87,12 @@ export interface SlackChannelConfig {
|
|
|
83
87
|
onInteraction?(action: SlackInteractionAction, ctx: SlackContext): void | Promise<void>;
|
|
84
88
|
readonly events?: SlackChannelEvents;
|
|
85
89
|
}
|
|
86
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Concrete return type of {@link slackChannel}. Named so consumers can
|
|
92
|
+
* default-export a `slackChannel(...)` call under `declaration: true`
|
|
93
|
+
* without TypeScript falling back to an internal path for `Channel`.
|
|
94
|
+
*/
|
|
95
|
+
export interface SlackChannel extends Channel<SlackChannelState> {
|
|
96
|
+
}
|
|
97
|
+
export declare function slackChannel(config?: SlackChannelConfig): SlackChannel;
|
|
87
98
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createLogger } from "#internal/logging.js";
|
|
2
|
-
import { buildSlackTurnMessage, collectSlackFileParts,
|
|
2
|
+
import { buildSlackTurnMessage, collectSlackFileParts, createSlackFetchFile, } from "#public/channels/slack/attachments.js";
|
|
3
3
|
import { deriveHitlResponse, isHitlAction, renderInputRequestBlocks, } from "#public/channels/slack/hitl.js";
|
|
4
4
|
import { mergeUploadPolicy } from "#public/channels/upload-policy.js";
|
|
5
|
-
import { defineChannel, POST
|
|
5
|
+
import { defineChannel, POST } from "#public/definitions/defineChannel.js";
|
|
6
6
|
const log = createLogger("slack.channel");
|
|
7
7
|
function decodeThreadId(id) {
|
|
8
8
|
const parts = id.replace(/^slack:/u, "").split(":");
|
|
@@ -109,10 +109,9 @@ function defaultInputRequestedHandler() {
|
|
|
109
109
|
}
|
|
110
110
|
export function slackChannel(config = {}) {
|
|
111
111
|
const uploadPolicy = mergeUploadPolicy(config.uploadPolicy);
|
|
112
|
-
const
|
|
112
|
+
const slackFetchFile = createSlackFetchFile({
|
|
113
113
|
botToken: config.credentials?.botToken,
|
|
114
114
|
});
|
|
115
|
-
let activeSend = null;
|
|
116
115
|
let chatPromise = null;
|
|
117
116
|
async function getChat() {
|
|
118
117
|
if (chatPromise)
|
|
@@ -139,39 +138,6 @@ export function slackChannel(config = {}) {
|
|
|
139
138
|
userName: config.botName ?? "ash-agent",
|
|
140
139
|
});
|
|
141
140
|
await chat.initialize();
|
|
142
|
-
chat.onNewMention(async (thread, message) => {
|
|
143
|
-
// Slack sends both `app_mention` and `message.channels` for the same message.
|
|
144
|
-
// The Chat SDK dedup relies on in-memory state that doesn't survive across
|
|
145
|
-
// serverless invocations, so both events reach this handler. Only process
|
|
146
|
-
// `app_mention` to prevent duplicate runs.
|
|
147
|
-
const rawEvent = message.raw;
|
|
148
|
-
if (rawEvent?.type !== "app_mention")
|
|
149
|
-
return;
|
|
150
|
-
const send = activeSend;
|
|
151
|
-
if (!send) {
|
|
152
|
-
throw new Error("slackChannel: mention received but no request context is active.");
|
|
153
|
-
}
|
|
154
|
-
const teamId = rawEvent.team_id ?? rawEvent.team;
|
|
155
|
-
const slackCtx = {
|
|
156
|
-
thread,
|
|
157
|
-
slack: buildSlackApiHandle(thread, config.credentials?.botToken, teamId),
|
|
158
|
-
};
|
|
159
|
-
const runOpts = config.run ? config.run(slackCtx, message) : { auth: null };
|
|
160
|
-
if (runOpts === null)
|
|
161
|
-
return;
|
|
162
|
-
const decoded = decodeThreadId(thread.id ?? "");
|
|
163
|
-
const continuationToken = `slack:${decoded.channelId}:${decoded.threadTs}`;
|
|
164
|
-
const fileParts = collectSlackFileParts(message, uploadPolicy);
|
|
165
|
-
const turnMessage = buildSlackTurnMessage(message.text, fileParts);
|
|
166
|
-
await send(turnMessage, {
|
|
167
|
-
auth: runOpts.auth,
|
|
168
|
-
continuationToken,
|
|
169
|
-
state: {
|
|
170
|
-
serializedThread: thread.toJSON(),
|
|
171
|
-
teamId: teamId ?? null,
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
141
|
return { chat };
|
|
176
142
|
})();
|
|
177
143
|
chatPromise = promise;
|
|
@@ -183,12 +149,12 @@ export function slackChannel(config = {}) {
|
|
|
183
149
|
const inputHandler = config.events?.["input.requested"] ?? defaultInputRequestedHandler();
|
|
184
150
|
return defineChannel({
|
|
185
151
|
state: { serializedThread: null, teamId: null },
|
|
186
|
-
|
|
152
|
+
fetchFile: slackFetchFile,
|
|
187
153
|
context(state) {
|
|
188
154
|
return rebuildSlackContext(state, config.credentials?.botToken);
|
|
189
155
|
},
|
|
190
156
|
routes: [
|
|
191
|
-
POST("/ash/v1/slack", async (req, { send, waitUntil }) => {
|
|
157
|
+
POST(config.route ?? "/ash/v1/slack", async (req, { send, waitUntil }) => {
|
|
192
158
|
const { chat } = await getChat();
|
|
193
159
|
const contentType = req.headers.get("content-type") ?? "";
|
|
194
160
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
@@ -249,16 +215,36 @@ export function slackChannel(config = {}) {
|
|
|
249
215
|
}
|
|
250
216
|
return new Response("ok", { status: 200 });
|
|
251
217
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
218
|
+
chat.onNewMention(async (thread, message) => {
|
|
219
|
+
const rawEvent = message.raw;
|
|
220
|
+
// Slack sends both `app_mention` and `message.channels` for the same
|
|
221
|
+
// utterance. The Chat SDK dedup relies on in-memory state that doesn't
|
|
222
|
+
// survive serverless invocations, so both events reach this handler.
|
|
223
|
+
// Only process `app_mention` to prevent duplicate runs.
|
|
224
|
+
if (rawEvent?.type !== "app_mention")
|
|
225
|
+
return;
|
|
226
|
+
const teamId = rawEvent.team_id ?? rawEvent.team;
|
|
227
|
+
const slackCtx = {
|
|
228
|
+
thread,
|
|
229
|
+
slack: buildSlackApiHandle(thread, config.credentials?.botToken, teamId),
|
|
230
|
+
};
|
|
231
|
+
const runOpts = config.run ? config.run(slackCtx, message) : { auth: null };
|
|
232
|
+
if (runOpts === null)
|
|
233
|
+
return;
|
|
234
|
+
const decoded = decodeThreadId(thread.id ?? "");
|
|
235
|
+
const continuationToken = `slack:${decoded.channelId}:${decoded.threadTs}`;
|
|
236
|
+
const fileParts = collectSlackFileParts(message, uploadPolicy);
|
|
237
|
+
const turnMessage = buildSlackTurnMessage(message.text, fileParts);
|
|
238
|
+
await send(turnMessage, {
|
|
239
|
+
auth: runOpts.auth,
|
|
240
|
+
continuationToken,
|
|
241
|
+
state: {
|
|
242
|
+
serializedThread: thread.toJSON(),
|
|
243
|
+
teamId: teamId ?? null,
|
|
244
|
+
},
|
|
257
245
|
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
activeSend = null;
|
|
261
|
-
}
|
|
246
|
+
});
|
|
247
|
+
return await chat.webhooks.slack(req, { waitUntil });
|
|
262
248
|
}),
|
|
263
249
|
],
|
|
264
250
|
async receive(input, { send }) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FetchFileResult } from "#channel/adapter.js";
|
|
2
2
|
import { CHANNEL_SENTINEL } from "#channel/compiled-channel.js";
|
|
3
3
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
4
4
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
@@ -7,7 +7,6 @@ import type { Session, SessionHandle } from "#channel/session.js";
|
|
|
7
7
|
export type { Session, SessionHandle } from "#channel/session.js";
|
|
8
8
|
export { POST, GET, PUT, DELETE } from "#channel/routes.js";
|
|
9
9
|
export type { RouteDefinition, RouteHandlerArgs, SendFn, SendOptions, SendPayload, GetSessionFn, } from "#channel/routes.js";
|
|
10
|
-
export type { AttachmentResolver } from "#channel/adapter.js";
|
|
11
10
|
type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessageStreamEvent, {
|
|
12
11
|
type: T;
|
|
13
12
|
}> extends {
|
|
@@ -43,12 +42,15 @@ export interface ChannelConfig<TState = undefined, TCtx = void> {
|
|
|
43
42
|
}): Promise<Session>;
|
|
44
43
|
readonly events?: ChannelEvents<TCtx>;
|
|
45
44
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
45
|
+
* Fetches bytes for a URL encountered in `FilePart.data`.
|
|
46
|
+
*
|
|
47
|
+
* Called by the staging pipeline when it encounters a `URL` object
|
|
48
|
+
* on a `FilePart` (e.g. a Slack file URL placed there by the route
|
|
49
|
+
* handler). Return `null` to let the URL pass through to the model
|
|
50
|
+
* provider unchanged. Return bytes or {@link FetchFileResult} to
|
|
51
|
+
* stage the file to the sandbox.
|
|
50
52
|
*/
|
|
51
|
-
readonly
|
|
53
|
+
readonly fetchFile?: (url: string) => Promise<Buffer | FetchFileResult | null>;
|
|
52
54
|
}
|
|
53
55
|
export interface Channel<TState = undefined> {
|
|
54
56
|
readonly __kind: typeof CHANNEL_SENTINEL;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CHANNEL_SENTINEL } from "#channel/compiled-channel.js";
|
|
2
2
|
import { defaultDeliverResult } from "#channel/adapter.js";
|
|
3
|
+
import { HTTP_ADAPTER_KIND } from "#channel/http.js";
|
|
3
4
|
export { POST, GET, PUT, DELETE } from "#channel/routes.js";
|
|
4
5
|
export function defineChannel(config) {
|
|
5
6
|
const adapter = buildAdapter(config);
|
|
@@ -14,7 +15,7 @@ export function defineChannel(config) {
|
|
|
14
15
|
function buildAdapter(config) {
|
|
15
16
|
const hasState = config.state != null;
|
|
16
17
|
const hasContext = config.context != null;
|
|
17
|
-
const
|
|
18
|
+
const hasFetchFile = config.fetchFile !== undefined;
|
|
18
19
|
const hasBehavior = hasState || hasContext;
|
|
19
20
|
const eventHandlers = {};
|
|
20
21
|
let hasEventHandlers = false;
|
|
@@ -42,20 +43,13 @@ function buildAdapter(config) {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// registry conflict when the adapter would otherwise reserve a
|
|
48
|
-
// framework kind with no behavior to register against it.
|
|
49
|
-
if (!hasBehavior && !hasEventHandlers && !hasAttachments) {
|
|
50
|
-
return { kind: "http" };
|
|
46
|
+
if (!hasBehavior && !hasEventHandlers && !hasFetchFile) {
|
|
47
|
+
return { kind: HTTP_ADAPTER_KIND };
|
|
51
48
|
}
|
|
52
|
-
// Channels with any authored behavior use a dedicated adapter kind so
|
|
53
|
-
// the registry can rehydrate it across step boundaries without
|
|
54
|
-
// conflicting with framework-reserved kinds.
|
|
55
49
|
return {
|
|
56
50
|
kind: "defineChannel",
|
|
57
51
|
state: hasState ? { ...config.state } : {},
|
|
58
|
-
|
|
52
|
+
fetchFile: config.fetchFile,
|
|
59
53
|
createAdapterContext(base) {
|
|
60
54
|
const state = base.state;
|
|
61
55
|
const channelCtx = hasContext ? config.context(state) : {};
|
|
@@ -2,9 +2,9 @@ import type { Optional } from "#shared/optional.js";
|
|
|
2
2
|
import type { SandboxSession } from "#shared/sandbox-session.js";
|
|
3
3
|
import type { SandboxDefinition as SharedSandboxDefinition } from "#shared/sandbox-definition.js";
|
|
4
4
|
export type { SandboxCommandOptions, SandboxCommandResult, SandboxReadFileOptions, SandboxSession, } from "#shared/sandbox-session.js";
|
|
5
|
-
export type {
|
|
6
|
-
export type SandboxDefinition<S extends SandboxSession = SandboxSession
|
|
5
|
+
export type { SandboxBootstrapUseOptions, SandboxBootstrapUseFn, SandboxSessionUseFn, SandboxBootstrapContext, SandboxSessionContext, } from "#shared/sandbox-definition.js";
|
|
6
|
+
export type SandboxDefinition<S extends SandboxSession = SandboxSession, O = Record<string, never>> = Optional<SharedSandboxDefinition<S, O>, "backend">;
|
|
7
7
|
/**
|
|
8
8
|
* Defines a sandbox configuration.
|
|
9
9
|
*/
|
|
10
|
-
export declare function defineSandbox<S extends SandboxSession = SandboxSession
|
|
10
|
+
export declare function defineSandbox<S extends SandboxSession = SandboxSession, O = Record<string, never>>(definition: SandboxDefinition<S, O>): SandboxDefinition<S, O>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
|
|
2
|
-
import type { VercelSandbox } from "#public/sandbox/vercel-sandbox.js";
|
|
2
|
+
import type { VercelSandbox, VercelSandboxSessionUseOptions } from "#public/sandbox/vercel-sandbox.js";
|
|
3
3
|
/**
|
|
4
4
|
* Constructs the built-in Vercel sandbox backend.
|
|
5
5
|
*
|
|
@@ -8,4 +8,4 @@ import type { VercelSandbox } from "#public/sandbox/vercel-sandbox.js";
|
|
|
8
8
|
* timeout) lives in `onSession`'s `create()` call or
|
|
9
9
|
* `VercelSandbox.update()`.
|
|
10
10
|
*/
|
|
11
|
-
export declare function vercelBackend(): SandboxBackend<VercelSandbox>;
|
|
11
|
+
export declare function vercelBackend(): SandboxBackend<VercelSandbox, VercelSandboxSessionUseOptions>;
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* `agent/sandbox/sandbox.ts` when paired with a `workspace/` folder).
|
|
4
4
|
*/
|
|
5
5
|
export { getSandbox } from "#context/accessors.js";
|
|
6
|
-
export { defineSandbox, type SandboxBootstrapContext, type SandboxBootstrapUseFn, type SandboxBootstrapUseOptions, type SandboxCommandOptions, type SandboxCommandResult, type SandboxDefinition, type
|
|
6
|
+
export { defineSandbox, type SandboxBootstrapContext, type SandboxBootstrapUseFn, type SandboxBootstrapUseOptions, type SandboxCommandOptions, type SandboxCommandResult, type SandboxDefinition, type SandboxReadFileOptions, type SandboxSession, type SandboxSessionContext, type SandboxSessionUseFn, } from "#public/definitions/sandbox.js";
|
|
7
7
|
export type { SandboxBackend, SandboxBackendCreateInput, SandboxBackendHandle, SandboxBackendPrewarmInput, SandboxBackendRuntimeContext, SandboxBackendSessionState, SandboxSeedFile, } from "#public/definitions/sandbox-backend.js";
|
|
8
8
|
export { SandboxTemplateNotProvisionedError } from "#public/definitions/sandbox-backend.js";
|
|
9
9
|
export { defaultBackend } from "#public/sandbox/backends/default.js";
|
|
10
10
|
export { localBackend } from "#public/sandbox/backends/local.js";
|
|
11
11
|
export { vercelBackend } from "#public/sandbox/backends/vercel.js";
|
|
12
|
-
export type { VercelSandbox } from "#public/sandbox/vercel-sandbox.js";
|
|
12
|
+
export type { VercelSandbox, VercelSandboxSessionUseOptions, } from "#public/sandbox/vercel-sandbox.js";
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SandboxUpdateParams } from "#compiled/@vercel/sandbox/index.js";
|
|
2
|
+
import type { SandboxSession } from "#public/definitions/sandbox.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options accepted by the Vercel backend's `onSession({ use })` hook
|
|
5
|
+
* and `VercelSandbox.update()`. Aliases the Vercel SDK's
|
|
6
|
+
* `SandboxUpdateParams` so the full live-session configuration surface
|
|
7
|
+
* is available to authors.
|
|
8
|
+
*/
|
|
9
|
+
export type VercelSandboxSessionUseOptions = SandboxUpdateParams;
|
|
2
10
|
/**
|
|
3
11
|
* Ash-owned session type returned by the Vercel backend's
|
|
4
12
|
* `onSession({ use })` hook. Extends the narrow
|
|
@@ -9,16 +17,9 @@ export interface VercelSandbox extends SandboxSession {
|
|
|
9
17
|
readonly name: string;
|
|
10
18
|
readonly persistent: boolean;
|
|
11
19
|
readonly status: string;
|
|
12
|
-
readonly networkPolicy:
|
|
20
|
+
readonly networkPolicy: SandboxUpdateParams["networkPolicy"];
|
|
13
21
|
readonly tags: Record<string, string> | undefined;
|
|
14
|
-
update(params:
|
|
15
|
-
networkPolicy?: SandboxNetworkPolicy;
|
|
16
|
-
resources?: {
|
|
17
|
-
vcpus?: number;
|
|
18
|
-
};
|
|
19
|
-
timeout?: number;
|
|
20
|
-
tags?: Record<string, string>;
|
|
21
|
-
}): Promise<void>;
|
|
22
|
+
update(params: VercelSandboxSessionUseOptions): Promise<void>;
|
|
22
23
|
stop(opts?: {
|
|
23
24
|
blocking?: boolean;
|
|
24
25
|
}): Promise<void>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getAdapterKind } from "#channel/adapter.js";
|
|
2
|
+
import { HTTP_ADAPTER } from "#channel/http.js";
|
|
3
|
+
import { SCHEDULE_ADAPTER } from "#channel/schedule.js";
|
|
2
4
|
import { SUBAGENT_ADAPTER } from "#execution/subagent-adapter.js";
|
|
3
5
|
import { RuntimeRegistryError } from "#internal/runtime-registry.js";
|
|
4
6
|
/**
|
|
@@ -6,7 +8,11 @@ import { RuntimeRegistryError } from "#internal/runtime-registry.js";
|
|
|
6
8
|
*
|
|
7
9
|
* Framework kinds cannot be shadowed with route-authored behavior.
|
|
8
10
|
*/
|
|
9
|
-
const FRAMEWORK_ADAPTERS = [
|
|
11
|
+
const FRAMEWORK_ADAPTERS = [
|
|
12
|
+
HTTP_ADAPTER,
|
|
13
|
+
SUBAGENT_ADAPTER,
|
|
14
|
+
SCHEDULE_ADAPTER,
|
|
15
|
+
];
|
|
10
16
|
/**
|
|
11
17
|
* Non-event-handler fields on a {@link ChannelAdapter}. Any other
|
|
12
18
|
* key on the adapter object corresponds to a stream event handler
|
|
@@ -20,7 +26,7 @@ const ADAPTER_NON_EVENT_FIELDS = new Set([
|
|
|
20
26
|
"deliver",
|
|
21
27
|
"contextProviders",
|
|
22
28
|
"createAdapterContext",
|
|
23
|
-
"
|
|
29
|
+
"fetchFile",
|
|
24
30
|
]);
|
|
25
31
|
/**
|
|
26
32
|
* Builds the runtime-owned adapter registry from framework adapters plus any
|
|
@@ -109,7 +115,7 @@ function carriesAdapterBehavior(adapter) {
|
|
|
109
115
|
if (adapter.deliver !== undefined) {
|
|
110
116
|
return true;
|
|
111
117
|
}
|
|
112
|
-
if (adapter.
|
|
118
|
+
if (adapter.fetchFile !== undefined) {
|
|
113
119
|
return true;
|
|
114
120
|
}
|
|
115
121
|
if (adapter.createAdapterContext !== undefined) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HTTP_ADAPTER_KIND } from "#channel/http.js";
|
|
1
2
|
import { normalizeChannelDefinition } from "#internal/authored-definition/channel.js";
|
|
2
3
|
import { toErrorMessage } from "#shared/errors.js";
|
|
3
4
|
import { createResolvedModuleSourceRef, loadResolvedModuleExport, ResolveAgentError, } from "#runtime/resolve-helpers.js";
|
|
@@ -30,7 +31,7 @@ export async function resolveChannelDefinition(definition, moduleMap, nodeId) {
|
|
|
30
31
|
});
|
|
31
32
|
const matchedRoute = channelDefinition.routes.find((r) => r.method.toUpperCase() === definition.method.toUpperCase() && r.path === definition.urlPath);
|
|
32
33
|
const adapter = channelDefinition.adapter;
|
|
33
|
-
if (adapter && adapter.kind !==
|
|
34
|
+
if (adapter && adapter.kind !== HTTP_ADAPTER_KIND) {
|
|
34
35
|
adapter.kind = `channel:${definition.name}`;
|
|
35
36
|
}
|
|
36
37
|
return {
|
|
@@ -7,9 +7,9 @@ import type { SandboxSession } from "#shared/sandbox-session.js";
|
|
|
7
7
|
* runtime orchestrator can persist reconnect metadata and release
|
|
8
8
|
* resources.
|
|
9
9
|
*/
|
|
10
|
-
export interface SandboxBackendHandle<S extends SandboxSession = SandboxSession
|
|
10
|
+
export interface SandboxBackendHandle<S extends SandboxSession = SandboxSession, O = Record<string, never>> {
|
|
11
11
|
readonly session: SandboxSession;
|
|
12
|
-
readonly useSessionFn: SandboxSessionUseFn<S>;
|
|
12
|
+
readonly useSessionFn: SandboxSessionUseFn<S, O>;
|
|
13
13
|
captureState(): Promise<SandboxBackendSessionState>;
|
|
14
14
|
dispose(): Promise<void>;
|
|
15
15
|
}
|
|
@@ -97,7 +97,7 @@ export interface SandboxBackendPrewarmInput {
|
|
|
97
97
|
* Each backend owns the full template-then-session lifecycle internally;
|
|
98
98
|
* callers only need a single `create` call.
|
|
99
99
|
*/
|
|
100
|
-
export interface SandboxBackend<S extends SandboxSession = SandboxSession
|
|
100
|
+
export interface SandboxBackend<S extends SandboxSession = SandboxSession, O = Record<string, never>> {
|
|
101
101
|
/**
|
|
102
102
|
* Stable identifier for this backend implementation.
|
|
103
103
|
*
|
|
@@ -113,7 +113,7 @@ export interface SandboxBackend<S extends SandboxSession = SandboxSession> {
|
|
|
113
113
|
* {@link SandboxTemplateNotProvisionedError} when the requested
|
|
114
114
|
* template is missing.
|
|
115
115
|
*/
|
|
116
|
-
create(input: SandboxBackendCreateInput): Promise<SandboxBackendHandle<S>>;
|
|
116
|
+
create(input: SandboxBackendCreateInput): Promise<SandboxBackendHandle<S, O>>;
|
|
117
117
|
/**
|
|
118
118
|
* Build-time prewarm hook. Ash invokes this for every authored
|
|
119
119
|
* sandbox in the compiled graph before serving traffic so the backend
|