experimental-ash 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -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/environment.d.ts +3 -2
- 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 +58 -15
- 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.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 +0 -1
- package/dist/src/public/channels/slack/slackChannel.js +3 -3
- 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
|
@@ -6,7 +6,7 @@ import { ASH_PACKAGE_NAME } from "#package-name.js";
|
|
|
6
6
|
let cachedPackageInfo;
|
|
7
7
|
// The package build stamps the published version into `dist` so bundled
|
|
8
8
|
// deployments can still report package metadata without resolving package.json.
|
|
9
|
-
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.
|
|
9
|
+
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.8.0";
|
|
10
10
|
const BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER = "__ASH_PACKAGE_VERSION_PLACEHOLDER__";
|
|
11
11
|
const WORKFLOW_MODULE_ALIASES = {
|
|
12
12
|
"workflow/api": "src/compiled/@workflow/core/runtime.js",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom URL scheme for serializing `URL` objects in `FilePart.data`
|
|
3
|
+
* across the queue boundary.
|
|
4
|
+
*
|
|
5
|
+
* Route handlers place `new URL("https://...")` on `FilePart.data`.
|
|
6
|
+
* Before the message crosses the queue, `send()` rewrites these as
|
|
7
|
+
* `ash-url:https://...` strings. After the queue, the staging pipeline
|
|
8
|
+
* reconstitutes them back to `URL` objects so `instanceof URL` checks
|
|
9
|
+
* work reliably.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ASH_URL_SCHEME = "ash-url:";
|
|
12
|
+
export declare function serializeUrlFilePart(url: URL): string;
|
|
13
|
+
export declare function isSerializedUrlFilePart(data: unknown): data is string;
|
|
14
|
+
export declare function deserializeUrlFilePart(data: string): URL;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom URL scheme for serializing `URL` objects in `FilePart.data`
|
|
3
|
+
* across the queue boundary.
|
|
4
|
+
*
|
|
5
|
+
* Route handlers place `new URL("https://...")` on `FilePart.data`.
|
|
6
|
+
* Before the message crosses the queue, `send()` rewrites these as
|
|
7
|
+
* `ash-url:https://...` strings. After the queue, the staging pipeline
|
|
8
|
+
* reconstitutes them back to `URL` objects so `instanceof URL` checks
|
|
9
|
+
* work reliably.
|
|
10
|
+
*/
|
|
11
|
+
export const ASH_URL_SCHEME = "ash-url:";
|
|
12
|
+
export function serializeUrlFilePart(url) {
|
|
13
|
+
return `${ASH_URL_SCHEME}${url.href}`;
|
|
14
|
+
}
|
|
15
|
+
export function isSerializedUrlFilePart(data) {
|
|
16
|
+
return typeof data === "string" && data.startsWith(ASH_URL_SCHEME);
|
|
17
|
+
}
|
|
18
|
+
export function deserializeUrlFilePart(data) {
|
|
19
|
+
return new URL(data.slice(ASH_URL_SCHEME.length));
|
|
20
|
+
}
|
|
@@ -5,6 +5,5 @@ import type { NitroBuildSurface, PreparedApplicationHost } from "#internal/nitro
|
|
|
5
5
|
* SDK endpoints into one Nitro host instance.
|
|
6
6
|
*/
|
|
7
7
|
export declare function configureNitroRoutes(nitro: Nitro, preparedHost: PreparedApplicationHost, input: {
|
|
8
|
-
homePageHtml: string;
|
|
9
8
|
surface: NitroBuildSurface;
|
|
10
9
|
}): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, relative } from "node:path";
|
|
3
|
-
import { ASH_HEALTH_ROUTE_PATH,
|
|
3
|
+
import { ASH_HEALTH_ROUTE_PATH, ASH_INFO_ROUTE_PATH } from "#protocol/routes.js";
|
|
4
4
|
import { normalizeEsmImportSpecifier, stringifyEsmImportSpecifier, } from "#internal/application/import-specifier.js";
|
|
5
5
|
import { resolvePackageRoot, resolvePackageSourceFilePath, resolveWorkflowModulePath, } from "#internal/application/package.js";
|
|
6
6
|
import { WorkflowBundleBuilder } from "#internal/workflow-bundle/builder.js";
|
|
@@ -118,7 +118,12 @@ function buildWorkflowFileHandlerSource(input) {
|
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
120
|
* Registers a virtual Nitro handler for a framework route that needs
|
|
121
|
-
* build-time config values (e.g.
|
|
121
|
+
* build-time config values (e.g. `appRoot`) baked in.
|
|
122
|
+
*
|
|
123
|
+
* The generated handler is invoked by Nitro with `(event)` and forwards
|
|
124
|
+
* `event.req` as the trailing argument to `${handlerExport}`, so the
|
|
125
|
+
* handler can run request-time auth, header inspection, etc. on top of
|
|
126
|
+
* its baked-in config.
|
|
122
127
|
*/
|
|
123
128
|
function addFrameworkVirtualHandler(nitro, input) {
|
|
124
129
|
const virtualId = `#ash-route${input.route}`;
|
|
@@ -130,7 +135,7 @@ function addFrameworkVirtualHandler(nitro, input) {
|
|
|
130
135
|
});
|
|
131
136
|
nitro.options.virtual[virtualId] = [
|
|
132
137
|
`import { ${input.handlerExport} } from ${modulePath};`,
|
|
133
|
-
`export default () => ${input.handlerExport}(${input.args});`,
|
|
138
|
+
`export default async (event) => ${input.handlerExport}(${input.args}, event.req);`,
|
|
134
139
|
].join("\n");
|
|
135
140
|
}
|
|
136
141
|
/**
|
|
@@ -183,28 +188,30 @@ export async function configureNitroRoutes(nitro, preparedHost, input) {
|
|
|
183
188
|
dev: nitro.options.dev,
|
|
184
189
|
});
|
|
185
190
|
if (includesApplicationRoutes(input.surface)) {
|
|
186
|
-
// Framework routes that need build-time config
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
// Framework routes that need no build-time config use physical handler
|
|
192
|
+
// files directly. The home page is a fully static, build-time-baked HTML
|
|
193
|
+
// string with no agent metadata, so it does not need to round-trip
|
|
194
|
+
// through the virtual-handler args plumbing.
|
|
195
|
+
registerHandler(nitro, {
|
|
196
|
+
handlerPath: resolvePackageSourceFilePath("src/internal/nitro/routes/index.ts"),
|
|
191
197
|
method: "GET",
|
|
192
|
-
modulePath: resolvePackageSourceFilePath("src/internal/nitro/routes/index.ts"),
|
|
193
198
|
route: "/",
|
|
194
199
|
});
|
|
195
|
-
addFrameworkVirtualHandler(nitro, {
|
|
196
|
-
args: JSON.stringify({ appRoot: artifactsConfig.appRoot }),
|
|
197
|
-
handlerExport: "handleHomePageDataRequest",
|
|
198
|
-
method: "GET",
|
|
199
|
-
modulePath: resolvePackageSourceFilePath("src/internal/nitro/routes/home.ts"),
|
|
200
|
-
route: ASH_HOME_ROUTE_PATH,
|
|
201
|
-
});
|
|
202
|
-
// Framework routes that need no config use physical handler files directly.
|
|
203
200
|
registerHandler(nitro, {
|
|
204
201
|
handlerPath: resolvePackageSourceFilePath("src/internal/nitro/routes/health.ts"),
|
|
205
202
|
method: "GET",
|
|
206
203
|
route: ASH_HEALTH_ROUTE_PATH,
|
|
207
204
|
});
|
|
205
|
+
// The agent info endpoint needs `appRoot` baked at build time (used by
|
|
206
|
+
// the disk-source fallback in dev) and runs request-time auth, so it
|
|
207
|
+
// stays a virtual handler.
|
|
208
|
+
addFrameworkVirtualHandler(nitro, {
|
|
209
|
+
args: JSON.stringify({ appRoot: artifactsConfig.appRoot }),
|
|
210
|
+
handlerExport: "handleAgentInfoRequest",
|
|
211
|
+
method: "GET",
|
|
212
|
+
modulePath: resolvePackageSourceFilePath("src/internal/nitro/routes/info.ts"),
|
|
213
|
+
route: ASH_INFO_ROUTE_PATH,
|
|
214
|
+
});
|
|
208
215
|
registerHandler(nitro, {
|
|
209
216
|
handlerPath: resolvePackageSourceFilePath("src/internal/nitro/routes/workflow-runs.ts"),
|
|
210
217
|
method: "GET",
|
|
@@ -3,7 +3,6 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { createNitro } from "nitro/builder";
|
|
5
5
|
import { ASH_PACKAGE_NAME } from "#package-name.js";
|
|
6
|
-
import { ASH_HOME_PAGE_UI_ROUTE_PREFIX } from "#protocol/routes.js";
|
|
7
6
|
import { resolvePackageSourceDirectoryPath, resolvePackageSourceFilePath, resolveWorkflowModulePath, } from "#internal/application/package.js";
|
|
8
7
|
import { prepareAshVersionedCacheDirectory, writeAshVersionedCacheMetadata, } from "#internal/application/cache-metadata.js";
|
|
9
8
|
import { resolveNitroBuildDirectory } from "#internal/application/paths.js";
|
|
@@ -11,7 +10,6 @@ import { createNitroArtifactsConfig, } from "#internal/nitro/host/artifacts-conf
|
|
|
11
10
|
import { createCompiledSandboxBackendPrunePlugin } from "#internal/nitro/host/compiled-sandbox-backend-prune-plugin.js";
|
|
12
11
|
import { configureNitroRoutes } from "#internal/nitro/host/configure-nitro-routes.js";
|
|
13
12
|
import { applyAshCronHandlerRoute } from "#internal/nitro/host/cron-handler-route.js";
|
|
14
|
-
import { loadHomePageWebAssets, } from "#internal/nitro/host/load-home-page-web-assets.js";
|
|
15
13
|
import { createNitroBundlerConfig } from "#internal/nitro/host/nitro-bundler-config.js";
|
|
16
14
|
import { addNitroRoutingImportSpecifierPlugin } from "#internal/nitro/host/nitro-routing-import-specifier-plugin.js";
|
|
17
15
|
import { registerScheduleTaskHandlers } from "#internal/nitro/host/schedule-task-routes.js";
|
|
@@ -371,7 +369,6 @@ function patchWorkflowTransformExcludePath(nitro, workflowBuildDir) {
|
|
|
371
369
|
*/
|
|
372
370
|
export async function createApplicationNitro(preparedHost, dev, options = {}) {
|
|
373
371
|
const surface = options.surface ?? "all";
|
|
374
|
-
const homePageWebAssets = await loadHomePageWebAssets();
|
|
375
372
|
// In dev mode, schedules are skipped unless explicitly enabled via
|
|
376
373
|
// `ash dev --schedules`. Production builds always register schedules.
|
|
377
374
|
const schedulesAllowed = !dev || options.schedules === true;
|
|
@@ -406,9 +403,7 @@ export async function createApplicationNitro(preparedHost, dev, options = {}) {
|
|
|
406
403
|
},
|
|
407
404
|
preset,
|
|
408
405
|
plugins: nitroPlugins,
|
|
409
|
-
publicAssets:
|
|
410
|
-
? createHomePagePublicAssetEntries(homePageWebAssets)
|
|
411
|
-
: [],
|
|
406
|
+
publicAssets: [],
|
|
412
407
|
scanDirs: includesWorkflowStepRegistrations(surface)
|
|
413
408
|
? [resolvePackageSourceDirectoryPath("src/execution")]
|
|
414
409
|
: undefined,
|
|
@@ -487,7 +482,6 @@ export async function createApplicationNitro(preparedHost, dev, options = {}) {
|
|
|
487
482
|
});
|
|
488
483
|
}
|
|
489
484
|
await configureNitroRoutes(nitro, preparedHost, {
|
|
490
|
-
homePageHtml: homePageWebAssets.homePageHtml,
|
|
491
485
|
surface,
|
|
492
486
|
});
|
|
493
487
|
if (includesWorkflowStepRegistrations(surface)) {
|
|
@@ -495,12 +489,3 @@ export async function createApplicationNitro(preparedHost, dev, options = {}) {
|
|
|
495
489
|
}
|
|
496
490
|
return nitro;
|
|
497
491
|
}
|
|
498
|
-
function createHomePagePublicAssetEntries(homePageWebAssets) {
|
|
499
|
-
return [
|
|
500
|
-
{
|
|
501
|
-
baseURL: ASH_HOME_PAGE_UI_ROUTE_PREFIX,
|
|
502
|
-
dir: homePageWebAssets.assetsDirectoryPath,
|
|
503
|
-
maxAge: 60 * 60 * 24 * 365,
|
|
504
|
-
},
|
|
505
|
-
];
|
|
506
|
-
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { AgentInfoData } from "#internal/nitro/routes/agent-info/load-agent-info-data.js";
|
|
2
|
+
/**
|
|
3
|
+
* One summary count shown in the agent info overview.
|
|
4
|
+
*/
|
|
5
|
+
export interface AgentInfoSummaryCount {
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly value: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One source-backed row in the agent info tables.
|
|
11
|
+
*/
|
|
12
|
+
export interface AgentInfoSourceRow {
|
|
13
|
+
readonly details: string;
|
|
14
|
+
readonly logicalPath: string;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly sourceKind: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One schedule row in the agent info schedule table.
|
|
20
|
+
*/
|
|
21
|
+
export interface AgentInfoScheduleRow {
|
|
22
|
+
readonly cron: string;
|
|
23
|
+
readonly logicalPath: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly preview: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One subagent row in the agent info subagent table.
|
|
29
|
+
*/
|
|
30
|
+
export interface AgentInfoSubagentRow {
|
|
31
|
+
readonly details: string;
|
|
32
|
+
readonly kind: "Local";
|
|
33
|
+
readonly logicalPath: string;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One endpoint row in the agent info endpoint table.
|
|
38
|
+
*/
|
|
39
|
+
export interface AgentInfoEndpointRow {
|
|
40
|
+
readonly method: "GET" | "POST";
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly notes: string;
|
|
43
|
+
readonly path: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Runtime overview section returned by the agent info route.
|
|
47
|
+
*/
|
|
48
|
+
export interface AgentInfoOverview {
|
|
49
|
+
readonly configMetadata: Readonly<Record<string, string>>;
|
|
50
|
+
readonly configSource: string;
|
|
51
|
+
readonly discoveryErrors: number;
|
|
52
|
+
readonly discoveryWarnings: number;
|
|
53
|
+
readonly modelId: string;
|
|
54
|
+
readonly subagentCount: number;
|
|
55
|
+
readonly instructionsPromptSource: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Instructions prompt section returned by the agent info route.
|
|
59
|
+
*/
|
|
60
|
+
export interface AgentInfoInstructionsPrompt {
|
|
61
|
+
readonly logicalPath: string;
|
|
62
|
+
readonly markdownPreview: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Full response payload returned by `GET /ash/v1/info`.
|
|
66
|
+
*/
|
|
67
|
+
export interface AgentInfoResponse {
|
|
68
|
+
readonly agentId: string;
|
|
69
|
+
readonly endpointRows: readonly AgentInfoEndpointRow[];
|
|
70
|
+
readonly heroSummary: string;
|
|
71
|
+
readonly messageRequestExample: string;
|
|
72
|
+
readonly overview: AgentInfoOverview;
|
|
73
|
+
readonly pageDescription: string;
|
|
74
|
+
readonly pageTitle: string;
|
|
75
|
+
readonly sandbox: AgentInfoSourceRow | null;
|
|
76
|
+
readonly schedules: readonly AgentInfoScheduleRow[];
|
|
77
|
+
readonly skills: readonly AgentInfoSourceRow[];
|
|
78
|
+
readonly subagents: readonly AgentInfoSubagentRow[];
|
|
79
|
+
readonly summaryCounts: readonly AgentInfoSummaryCount[];
|
|
80
|
+
readonly instructions: AgentInfoSourceRow | null;
|
|
81
|
+
readonly instructionsPrompt: AgentInfoInstructionsPrompt;
|
|
82
|
+
readonly tools: readonly AgentInfoSourceRow[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Builds the JSON payload served by `GET /ash/v1/info`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function buildAgentInfoResponse(data: AgentInfoData): AgentInfoResponse;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ASH_CONTINUE_SESSION_ROUTE_PATTERN, ASH_CREATE_SESSION_ROUTE_PATH, ASH_HEALTH_ROUTE_PATH,
|
|
1
|
+
import { ASH_CONTINUE_SESSION_ROUTE_PATTERN, ASH_CREATE_SESSION_ROUTE_PATH, ASH_HEALTH_ROUTE_PATH, ASH_INFO_ROUTE_PATH, ASH_MESSAGE_STREAM_ROUTE_PATTERN, } from "#protocol/routes.js";
|
|
2
2
|
function pluralize(count, noun) {
|
|
3
3
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
4
4
|
}
|
|
@@ -90,9 +90,9 @@ function renderEndpointRows() {
|
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
method: "GET",
|
|
93
|
-
name: "
|
|
94
|
-
notes: "Inspection JSON
|
|
95
|
-
path:
|
|
93
|
+
name: "Agent info",
|
|
94
|
+
notes: "Inspection JSON for the deployed agent. Requires Vercel OIDC auth on Vercel.",
|
|
95
|
+
path: ASH_INFO_ROUTE_PATH,
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
method: "POST",
|
|
@@ -115,9 +115,9 @@ function renderEndpointRows() {
|
|
|
115
115
|
];
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
|
-
* Builds the JSON payload served
|
|
118
|
+
* Builds the JSON payload served by `GET /ash/v1/info`.
|
|
119
119
|
*/
|
|
120
|
-
export function
|
|
120
|
+
export function buildAgentInfoResponse(data) {
|
|
121
121
|
const instructions = data.agent.instructions;
|
|
122
122
|
const requestExample = `POST ${ASH_CREATE_SESSION_ROUTE_PATH}
|
|
123
123
|
content-type: application/json
|
|
@@ -2,25 +2,25 @@ import type { CompiledAgentManifest, CompiledSubagentNode } from "#compiler/mani
|
|
|
2
2
|
import { type RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
|
|
3
3
|
import type { ResolvedAgent, ResolvedSandboxDefinition, ResolvedSchedule, ResolvedSkillDefinition, ResolvedInstructions } from "#runtime/types.js";
|
|
4
4
|
/**
|
|
5
|
-
* Runtime data needed to build the package-owned
|
|
5
|
+
* Runtime data needed to build the package-owned `GET /ash/v1/info`
|
|
6
|
+
* inspection JSON.
|
|
6
7
|
*/
|
|
7
|
-
export interface
|
|
8
|
+
export interface AgentInfoData {
|
|
8
9
|
readonly agent: ResolvedAgent;
|
|
9
10
|
readonly manifest: CompiledAgentManifest;
|
|
10
11
|
readonly schedules: readonly ResolvedSchedule[];
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
|
-
* Loads the resolved runtime data
|
|
14
|
-
* homepage.
|
|
14
|
+
* Loads the resolved runtime data projected by `GET /ash/v1/info`.
|
|
15
15
|
*/
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function loadAgentInfoData(input: {
|
|
17
17
|
readonly compiledArtifactsSource: RuntimeCompiledArtifactsSource;
|
|
18
|
-
}): Promise<
|
|
18
|
+
}): Promise<AgentInfoData>;
|
|
19
19
|
/**
|
|
20
20
|
* Resolves the explicit runtime artifact source used by the package-owned
|
|
21
|
-
*
|
|
21
|
+
* `GET /ash/v1/info` handler.
|
|
22
22
|
*/
|
|
23
|
-
export declare function
|
|
23
|
+
export declare function resolveAgentInfoCompiledArtifactsSource(input?: {
|
|
24
24
|
readonly appRoot?: string;
|
|
25
25
|
}): RuntimeCompiledArtifactsSource;
|
|
26
26
|
export type { CompiledAgentManifest, CompiledSubagentNode, ResolvedSandboxDefinition, ResolvedSchedule, ResolvedSkillDefinition, ResolvedInstructions, };
|
|
@@ -5,26 +5,25 @@ import { loadCompiledModuleMap } from "#runtime/loaders/module-map.js";
|
|
|
5
5
|
import { resolveAgent } from "#runtime/resolve-agent.js";
|
|
6
6
|
import { resolveSchedules } from "#runtime/schedules/resolve-schedule.js";
|
|
7
7
|
/**
|
|
8
|
-
* Loads the resolved runtime data
|
|
9
|
-
* homepage.
|
|
8
|
+
* Loads the resolved runtime data projected by `GET /ash/v1/info`.
|
|
10
9
|
*/
|
|
11
|
-
export async function
|
|
12
|
-
return await
|
|
10
|
+
export async function loadAgentInfoData(input) {
|
|
11
|
+
return await loadAgentInfoDataFromArtifacts(input.compiledArtifactsSource);
|
|
13
12
|
}
|
|
14
13
|
/**
|
|
15
14
|
* Resolves the explicit runtime artifact source used by the package-owned
|
|
16
|
-
*
|
|
15
|
+
* `GET /ash/v1/info` handler.
|
|
17
16
|
*/
|
|
18
|
-
export function
|
|
17
|
+
export function resolveAgentInfoCompiledArtifactsSource(input = {}) {
|
|
19
18
|
if (readBundledCompiledArtifacts() !== null) {
|
|
20
19
|
return createBundledRuntimeCompiledArtifactsSource();
|
|
21
20
|
}
|
|
22
21
|
if (input.appRoot !== undefined) {
|
|
23
22
|
return createDiskRuntimeCompiledArtifactsSource(input.appRoot);
|
|
24
23
|
}
|
|
25
|
-
throw new Error("Ash
|
|
24
|
+
throw new Error("Ash agent info runtime data requires bundled artifacts or an app root.");
|
|
26
25
|
}
|
|
27
|
-
async function
|
|
26
|
+
async function loadAgentInfoDataFromArtifacts(compiledArtifactsSource) {
|
|
28
27
|
const [manifest, moduleMap] = await Promise.all([
|
|
29
28
|
loadCompiledManifest({
|
|
30
29
|
compiledArtifactsSource,
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import type { H3Event } from "nitro";
|
|
1
2
|
/**
|
|
2
|
-
* Builds the
|
|
3
|
+
* Builds the barebones home page response for one request. Exposed
|
|
4
|
+
* for tests so callers can supply a real {@link Request}; production
|
|
5
|
+
* traffic flows through the Nitro {@link H3Event} default export.
|
|
3
6
|
*/
|
|
4
|
-
export declare function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
7
|
+
export declare function buildHomePageResponse(request: Request): Response;
|
|
8
|
+
/**
|
|
9
|
+
* Nitro route handler for `GET /`. Adapts the Nitro event shape into
|
|
10
|
+
* {@link buildHomePageResponse}.
|
|
11
|
+
*/
|
|
12
|
+
export default function handleHomePageRequest(event: H3Event): Response;
|
|
@@ -1,31 +1,238 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Public docs URL surfaced from the barebones home page. Kept in source
|
|
3
|
+
* so the deployment output is a fully static, build-time-baked HTML
|
|
4
|
+
* response that performs no runtime resolution.
|
|
5
|
+
*/
|
|
6
|
+
const ASH_DOCS_URL = "https://ash.labs.vercel.dev/";
|
|
7
|
+
const DEPLOYMENT_URL_PLACEHOLDER = "{{DEPLOYMENT_URL}}";
|
|
8
|
+
/**
|
|
9
|
+
* Barebones HTML served at `GET /`.
|
|
10
|
+
*
|
|
11
|
+
* Reveals no information about the deployed agent — no name, no model,
|
|
12
|
+
* no instructions, no list of skills or schedules, no API endpoint paths.
|
|
13
|
+
* This is intentional: the root URL of a deployment is reachable by
|
|
14
|
+
* anyone on the public internet, and the deployment must not advertise
|
|
15
|
+
* its agent's configuration to unauthenticated callers. Inspection JSON
|
|
16
|
+
* (model id, instructions preview, skills, etc.) lives behind default
|
|
17
|
+
* Vercel OIDC auth on `/ash/v1/info`.
|
|
18
|
+
*
|
|
19
|
+
* The page also loads zero external assets — no fonts, no scripts, no
|
|
20
|
+
* images, no analytics beacons — so it cannot leak the deployment's
|
|
21
|
+
* origin to a third party simply by being visited.
|
|
22
|
+
*
|
|
23
|
+
* `{{DEPLOYMENT_URL}}` is the only request-time substitution: the page
|
|
24
|
+
* echoes the visitor's own request origin back into the `$ ash dev …`
|
|
25
|
+
* hint so they can copy-paste it without typing the URL by hand. We
|
|
26
|
+
* don't read any other request state.
|
|
27
|
+
*/
|
|
28
|
+
const HOME_PAGE_HTML_TEMPLATE = `<!doctype html>
|
|
29
|
+
<html lang="en">
|
|
30
|
+
<head>
|
|
31
|
+
<meta charset="utf-8">
|
|
32
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
33
|
+
<meta name="robots" content="noindex">
|
|
34
|
+
<meta name="referrer" content="no-referrer">
|
|
35
|
+
<title>ash</title>
|
|
36
|
+
<style>
|
|
37
|
+
:root {
|
|
38
|
+
color-scheme: light dark;
|
|
39
|
+
--bg: #fff;
|
|
40
|
+
--fg: #0a0a0a;
|
|
41
|
+
--muted: #6b6b6b;
|
|
42
|
+
--faint: #999;
|
|
43
|
+
--border: rgba(0, 0, 0, 0.09);
|
|
44
|
+
--surface: rgba(0, 0, 0, 0.025);
|
|
45
|
+
--accent: #00c46a;
|
|
46
|
+
--accent-glow: rgba(0, 196, 106, 0.18);
|
|
47
|
+
--button-bg: #0a0a0a;
|
|
48
|
+
--button-fg: #fff;
|
|
49
|
+
}
|
|
50
|
+
@media (prefers-color-scheme: dark) {
|
|
51
|
+
:root {
|
|
52
|
+
--bg: #000;
|
|
53
|
+
--fg: #ededed;
|
|
54
|
+
--muted: #8f8f8f;
|
|
55
|
+
--faint: #666;
|
|
56
|
+
--border: rgba(255, 255, 255, 0.1);
|
|
57
|
+
--surface: rgba(255, 255, 255, 0.035);
|
|
58
|
+
--accent: #46d4a4;
|
|
59
|
+
--accent-glow: rgba(70, 212, 164, 0.22);
|
|
60
|
+
--button-bg: #fff;
|
|
61
|
+
--button-fg: #000;
|
|
4
62
|
}
|
|
5
|
-
|
|
63
|
+
}
|
|
64
|
+
* { box-sizing: border-box; }
|
|
65
|
+
html, body { height: 100%; margin: 0; }
|
|
66
|
+
body {
|
|
67
|
+
background: var(--bg);
|
|
68
|
+
color: var(--fg);
|
|
69
|
+
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
70
|
+
Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
71
|
+
font-feature-settings: "cv11", "ss01";
|
|
72
|
+
font-size: 15px;
|
|
73
|
+
line-height: 1.55;
|
|
74
|
+
-webkit-font-smoothing: antialiased;
|
|
75
|
+
-moz-osx-font-smoothing: grayscale;
|
|
76
|
+
text-rendering: optimizeLegibility;
|
|
77
|
+
display: grid;
|
|
78
|
+
place-items: center;
|
|
79
|
+
padding: 2rem;
|
|
80
|
+
}
|
|
81
|
+
.mono {
|
|
82
|
+
font-family: ui-monospace, "SF Mono", "Menlo", "JetBrains Mono",
|
|
83
|
+
"Cascadia Code", Consolas, "Liberation Mono", monospace;
|
|
84
|
+
font-feature-settings: "zero", "ss01";
|
|
85
|
+
}
|
|
86
|
+
main {
|
|
87
|
+
width: 100%;
|
|
88
|
+
max-width: 32rem;
|
|
89
|
+
text-align: center;
|
|
90
|
+
}
|
|
91
|
+
.status {
|
|
92
|
+
display: inline-flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 0.5rem;
|
|
95
|
+
padding: 0.3125rem 0.75rem;
|
|
96
|
+
background: var(--surface);
|
|
97
|
+
border: 1px solid var(--border);
|
|
98
|
+
border-radius: 9999px;
|
|
99
|
+
font-size: 0.75rem;
|
|
100
|
+
color: var(--muted);
|
|
101
|
+
margin: 0 0 2rem;
|
|
102
|
+
}
|
|
103
|
+
.status-dot {
|
|
104
|
+
width: 6px;
|
|
105
|
+
height: 6px;
|
|
106
|
+
border-radius: 50%;
|
|
107
|
+
background: var(--accent);
|
|
108
|
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
109
|
+
flex-shrink: 0;
|
|
110
|
+
}
|
|
111
|
+
h1 {
|
|
112
|
+
margin: 0 0 0.875rem;
|
|
113
|
+
font-size: clamp(2.5rem, 9vw, 3.25rem);
|
|
114
|
+
font-weight: 500;
|
|
115
|
+
letter-spacing: -0.05em;
|
|
116
|
+
line-height: 1;
|
|
117
|
+
}
|
|
118
|
+
.lede {
|
|
119
|
+
margin: 0 0 1.5rem;
|
|
120
|
+
color: var(--muted);
|
|
121
|
+
font-size: 0.9375rem;
|
|
122
|
+
}
|
|
123
|
+
.lede a {
|
|
124
|
+
color: var(--fg);
|
|
125
|
+
text-decoration: underline;
|
|
126
|
+
text-decoration-thickness: 1px;
|
|
127
|
+
text-underline-offset: 3px;
|
|
128
|
+
text-decoration-color: var(--border);
|
|
129
|
+
transition: text-decoration-color 0.15s ease;
|
|
130
|
+
white-space: nowrap;
|
|
131
|
+
}
|
|
132
|
+
.lede a:hover { text-decoration-color: var(--fg); }
|
|
133
|
+
.lede-arrow {
|
|
134
|
+
display: inline-block;
|
|
135
|
+
margin-left: 0.125rem;
|
|
136
|
+
transition: transform 0.15s ease;
|
|
137
|
+
}
|
|
138
|
+
.lede a:hover .lede-arrow { transform: translateX(2px); }
|
|
139
|
+
.terminal {
|
|
140
|
+
display: inline-flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: 0.625rem;
|
|
143
|
+
width: 100%;
|
|
144
|
+
max-width: 28rem;
|
|
145
|
+
padding: 0.75rem 1rem;
|
|
146
|
+
background: var(--surface);
|
|
147
|
+
border: 1px solid var(--border);
|
|
148
|
+
border-radius: 0.5rem;
|
|
149
|
+
text-align: left;
|
|
150
|
+
font-size: 0.8125rem;
|
|
151
|
+
margin: 0 auto;
|
|
152
|
+
overflow-x: auto;
|
|
153
|
+
white-space: nowrap;
|
|
154
|
+
}
|
|
155
|
+
.terminal-prompt {
|
|
156
|
+
color: var(--faint);
|
|
157
|
+
user-select: none;
|
|
158
|
+
flex-shrink: 0;
|
|
159
|
+
}
|
|
160
|
+
.terminal-cmd { color: var(--fg); }
|
|
161
|
+
</style>
|
|
162
|
+
</head>
|
|
163
|
+
<body>
|
|
164
|
+
<main>
|
|
165
|
+
<span class="status mono">
|
|
166
|
+
<span class="status-dot" aria-hidden="true"></span>
|
|
167
|
+
running
|
|
168
|
+
</span>
|
|
169
|
+
<h1 class="mono">ash</h1>
|
|
170
|
+
<p class="lede">The agent is up and accepting messages. <a href="${ASH_DOCS_URL}">Read the docs<span class="lede-arrow" aria-hidden="true"> →</span></a></p>
|
|
171
|
+
<div class="terminal mono" role="group" aria-label="Send a message from your terminal">
|
|
172
|
+
<span class="terminal-prompt" aria-hidden="true">$</span>
|
|
173
|
+
<span class="terminal-cmd">ash dev ${DEPLOYMENT_URL_PLACEHOLDER}</span>
|
|
174
|
+
</div>
|
|
175
|
+
</main>
|
|
176
|
+
</body>
|
|
177
|
+
</html>
|
|
178
|
+
`;
|
|
179
|
+
function escapeHtml(value) {
|
|
180
|
+
return value
|
|
181
|
+
.replaceAll("&", "&")
|
|
182
|
+
.replaceAll("<", "<")
|
|
183
|
+
.replaceAll(">", ">")
|
|
184
|
+
.replaceAll('"', """)
|
|
185
|
+
.replaceAll("'", "'");
|
|
6
186
|
}
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return input.homePageHtml.replace("</head>", `${runtimeFlagsScript}</head>`);
|
|
187
|
+
function pickFirstForwardedValue(value) {
|
|
188
|
+
if (value === null) {
|
|
189
|
+
return undefined;
|
|
11
190
|
}
|
|
12
|
-
|
|
13
|
-
|
|
191
|
+
const first = value.split(",")[0]?.trim();
|
|
192
|
+
if (first === undefined || first.length === 0) {
|
|
193
|
+
return undefined;
|
|
14
194
|
}
|
|
15
|
-
return
|
|
195
|
+
return first;
|
|
16
196
|
}
|
|
17
197
|
/**
|
|
18
|
-
*
|
|
198
|
+
* Resolves the public origin a visitor is using to reach the deployment.
|
|
199
|
+
*
|
|
200
|
+
* Prefers the `x-forwarded-host` / `x-forwarded-proto` headers set by
|
|
201
|
+
* Vercel's edge so the rendered URL matches the address the visitor
|
|
202
|
+
* actually typed (including custom domains), then falls back to the
|
|
203
|
+
* `host` header, then to `request.url` for local `ash dev` runs that
|
|
204
|
+
* skip the proxy chain. Comma-separated forwarded values are split and
|
|
205
|
+
* the first hop is used — that is the public-facing entry, the rest are
|
|
206
|
+
* internal forwarder hostnames.
|
|
19
207
|
*/
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
208
|
+
function resolveDeploymentUrl(request) {
|
|
209
|
+
const headers = request.headers;
|
|
210
|
+
const requestUrl = new URL(request.url);
|
|
211
|
+
const forwardedHost = pickFirstForwardedValue(headers.get("x-forwarded-host"));
|
|
212
|
+
const forwardedProto = pickFirstForwardedValue(headers.get("x-forwarded-proto"));
|
|
213
|
+
const host = forwardedHost ?? headers.get("host") ?? requestUrl.host;
|
|
214
|
+
const proto = forwardedProto ?? requestUrl.protocol.replace(/:$/, "");
|
|
215
|
+
return `${proto}://${host}`;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Builds the barebones home page response for one request. Exposed
|
|
219
|
+
* for tests so callers can supply a real {@link Request}; production
|
|
220
|
+
* traffic flows through the Nitro {@link H3Event} default export.
|
|
221
|
+
*/
|
|
222
|
+
export function buildHomePageResponse(request) {
|
|
223
|
+
const deploymentUrl = resolveDeploymentUrl(request);
|
|
224
|
+
const html = HOME_PAGE_HTML_TEMPLATE.replace(DEPLOYMENT_URL_PLACEHOLDER, escapeHtml(deploymentUrl));
|
|
225
|
+
return new Response(html, {
|
|
26
226
|
headers: {
|
|
27
227
|
"cache-control": "no-store",
|
|
28
228
|
"content-type": "text/html; charset=utf-8",
|
|
29
229
|
},
|
|
30
230
|
});
|
|
31
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Nitro route handler for `GET /`. Adapts the Nitro event shape into
|
|
234
|
+
* {@link buildHomePageResponse}.
|
|
235
|
+
*/
|
|
236
|
+
export default function handleHomePageRequest(event) {
|
|
237
|
+
return buildHomePageResponse(event.req);
|
|
238
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the package-owned JSON inspection response for the current
|
|
3
|
+
* agent (model id, instructions preview, skills, schedules, subagents,
|
|
4
|
+
* tools, sandbox, endpoint catalog).
|
|
5
|
+
*
|
|
6
|
+
* The route is gated by {@link resolveAgentInfoAuth} so the deployed URL
|
|
7
|
+
* does not expose agent metadata to anonymous callers. The same-project
|
|
8
|
+
* bypass on `vercelOidc()` keeps the route reachable to in-deployment
|
|
9
|
+
* callers (`apps/nextjs-ash`-style consumers) without any additional
|
|
10
|
+
* setup.
|
|
11
|
+
*/
|
|
12
|
+
export declare function handleAgentInfoRequest(input: {
|
|
13
|
+
appRoot?: string;
|
|
14
|
+
}, request: Request): Promise<Response>;
|