experimental-ash 0.20.1 → 0.22.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/docs/public/channels/README.md +3 -1
  3. package/dist/docs/public/hooks.md +76 -6
  4. package/dist/docs/public/skills.md +6 -1
  5. package/dist/docs/public/tools.md +4 -0
  6. package/dist/src/chunks/{dev-authored-source-watcher-BR7XYOW0.js → dev-authored-source-watcher-DKDaaPea.js} +1 -1
  7. package/dist/src/chunks/{host-Dxf7CGaE.js → host-Btr4S69C.js} +7 -7
  8. package/dist/src/chunks/paths-DZTgjrW-.js +88 -0
  9. package/dist/src/chunks/{prewarm-vAT2QBqc.js → prewarm-BELT37PI.js} +1 -1
  10. package/dist/src/cli/commands/info.js +1 -1
  11. package/dist/src/cli/run.js +1 -1
  12. package/dist/src/compiled/.vendor-stamp.json +3 -3
  13. package/dist/src/compiled/@ai-sdk/anthropic/_provider-utils.d.ts +15 -0
  14. package/dist/src/compiled/@ai-sdk/anthropic/index.d.ts +1259 -6
  15. package/dist/src/compiled/@ai-sdk/google/_provider-utils.d.ts +11 -0
  16. package/dist/src/compiled/@ai-sdk/google/index.d.ts +561 -6
  17. package/dist/src/compiled/@ai-sdk/google/index.js +4 -4
  18. package/dist/src/compiled/@ai-sdk/google/package.json +1 -1
  19. package/dist/src/compiled/@ai-sdk/openai/_provider-utils.d.ts +15 -0
  20. package/dist/src/compiled/@ai-sdk/openai/index.d.ts +1255 -6
  21. package/dist/src/compiled/@ai-sdk/openai/index.js +1 -1
  22. package/dist/src/compiled/@ai-sdk/openai/package.json +1 -1
  23. package/dist/src/compiler/normalize-skill.js +5 -1
  24. package/dist/src/compiler/workspace-resources.js +12 -5
  25. package/dist/src/context/hook-lifecycle.d.ts +8 -8
  26. package/dist/src/context/hook-lifecycle.js +88 -36
  27. package/dist/src/evals/cli/eval.js +1 -1
  28. package/dist/src/execution/skills/instructions.d.ts +8 -4
  29. package/dist/src/execution/skills/instructions.js +3 -2
  30. package/dist/src/harness/attachment-staging.js +5 -1
  31. package/dist/src/harness/tool-loop.js +11 -3
  32. package/dist/src/internal/application/package.js +1 -1
  33. package/dist/src/internal/authored-definition/core.js +16 -1
  34. package/dist/src/internal/nitro/routes/workflow-route-helpers.d.ts +4 -4
  35. package/dist/src/internal/nitro/routes/workflow-route-helpers.js +6 -5
  36. package/dist/src/internal/nitro/routes/workflow-run-events.d.ts +1 -1
  37. package/dist/src/internal/nitro/routes/workflow-run-events.js +1 -1
  38. package/dist/src/internal/nitro/routes/workflow-run-steps.d.ts +1 -1
  39. package/dist/src/internal/nitro/routes/workflow-run-steps.js +1 -1
  40. package/dist/src/internal/nitro/routes/workflow-run.d.ts +1 -1
  41. package/dist/src/internal/nitro/routes/workflow-run.js +1 -1
  42. package/dist/src/internal/nitro/routes/workflow-runs.d.ts +1 -1
  43. package/dist/src/internal/nitro/routes/workflow-runs.js +1 -1
  44. package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +2 -0
  45. package/dist/src/public/definitions/hook.d.ts +11 -3
  46. package/dist/src/public/definitions/skill.d.ts +4 -3
  47. package/dist/src/public/definitions/skill.js +1 -1
  48. package/dist/src/public/next/index.d.ts +74 -0
  49. package/dist/src/public/next/index.js +150 -0
  50. package/dist/src/public/next/server.d.ts +6 -0
  51. package/dist/src/public/next/server.js +348 -0
  52. package/dist/src/public/next/vercel-json.d.ts +8 -0
  53. package/dist/src/public/next/vercel-json.js +76 -0
  54. package/dist/src/public/skills/index.d.ts +1 -1
  55. package/dist/src/public/skills/index.js +1 -1
  56. package/dist/src/public/tool-result-narrowing.d.ts +68 -0
  57. package/dist/src/public/tool-result-narrowing.js +58 -0
  58. package/dist/src/public/tools/index.d.ts +1 -0
  59. package/dist/src/public/tools/index.js +1 -0
  60. package/dist/src/runtime/resolve-connection.js +2 -0
  61. package/dist/src/runtime/resolve-tool.js +2 -0
  62. package/dist/src/shared/skill-definition.d.ts +20 -2
  63. package/dist/src/shared/skill-package.d.ts +43 -0
  64. package/dist/src/shared/skill-package.js +100 -0
  65. package/package.json +13 -3
  66. package/dist/src/chunks/paths-BcM3el4Q.js +0 -88
@@ -0,0 +1,150 @@
1
+ import { isAbsolute, resolve } from "node:path";
2
+ import { ASH_ROUTE_PREFIX } from "#protocol/routes.js";
3
+ import { resolveAshDestinationPrefix } from "./server.js";
4
+ import { ensureAshVercelJson } from "./vercel-json.js";
5
+ /**
6
+ * Private route namespace used when a Vercel deployment hosts Ash as a
7
+ * separate experimental service behind the Next.js app.
8
+ */
9
+ export const ASH_NEXT_SERVICE_PREFIX = "/_ash_internal/ash";
10
+ const ASH_NEXT_PRODUCTION_ORIGIN_ENV = "ASH_NEXT_PRODUCTION_ORIGIN";
11
+ const ASH_NEXT_PRODUCTION_PORT_ENV = "ASH_NEXT_PRODUCTION_PORT";
12
+ const DEFAULT_ASH_NEXT_PRODUCTION_PORT = 4274;
13
+ const ASH_PROXY_REWRITE_SOURCES = [`${ASH_ROUTE_PREFIX}/:path+`];
14
+ function resolveApplicationRoot(appPath) {
15
+ if (appPath === undefined || appPath.length === 0) {
16
+ return process.cwd();
17
+ }
18
+ return isAbsolute(appPath) ? appPath : resolve(process.cwd(), appPath);
19
+ }
20
+ function normalizeRoutePrefix(prefix) {
21
+ const prefixed = prefix.startsWith("/") ? prefix : `/${prefix}`;
22
+ const normalized = prefixed.replace(/\/+$/, "");
23
+ if (normalized.length === 0) {
24
+ throw new Error("Ash Next.js service prefix cannot resolve to the root route.");
25
+ }
26
+ return normalized;
27
+ }
28
+ function joinRoutePrefix(prefix, path) {
29
+ return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
30
+ }
31
+ function normalizeOrigin(origin) {
32
+ return new URL(origin.trim()).origin;
33
+ }
34
+ function hasUrlScheme(value) {
35
+ return /^[a-z][a-z\d+.-]*:/i.test(value);
36
+ }
37
+ function resolveVercelDeploymentOrigin() {
38
+ const deploymentUrl = process.env.VERCEL_URL;
39
+ if (deploymentUrl === undefined || deploymentUrl.trim().length === 0) {
40
+ return undefined;
41
+ }
42
+ return normalizeOrigin(hasUrlScheme(deploymentUrl) ? deploymentUrl : `https://${deploymentUrl}`);
43
+ }
44
+ function readLocalProductionPort() {
45
+ const configuredPort = process.env[ASH_NEXT_PRODUCTION_PORT_ENV];
46
+ if (configuredPort === undefined || configuredPort.trim().length === 0) {
47
+ return DEFAULT_ASH_NEXT_PRODUCTION_PORT;
48
+ }
49
+ const port = Number.parseInt(configuredPort, 10);
50
+ if (String(port) !== configuredPort.trim() || port < 1 || port > 65_535) {
51
+ throw new Error(`${ASH_NEXT_PRODUCTION_PORT_ENV} must be an integer between 1 and 65535.`);
52
+ }
53
+ return port;
54
+ }
55
+ function resolveProductionDestination(servicePrefix) {
56
+ const configuredOrigin = process.env[ASH_NEXT_PRODUCTION_ORIGIN_ENV];
57
+ if (configuredOrigin !== undefined && configuredOrigin.trim().length > 0) {
58
+ return {
59
+ destinationPrefix: joinRoutePrefix(normalizeOrigin(configuredOrigin), servicePrefix),
60
+ };
61
+ }
62
+ const vercelOrigin = resolveVercelDeploymentOrigin();
63
+ if (vercelOrigin !== undefined) {
64
+ return {
65
+ destinationPrefix: joinRoutePrefix(vercelOrigin, servicePrefix),
66
+ };
67
+ }
68
+ const localServerOrigin = `http://127.0.0.1:${String(readLocalProductionPort())}`;
69
+ return {
70
+ destinationPrefix: localServerOrigin,
71
+ localServerOrigin,
72
+ };
73
+ }
74
+ function createAshRewriteRules(destinationPrefix) {
75
+ return ASH_PROXY_REWRITE_SOURCES.map((source) => ({
76
+ basePath: false,
77
+ destination: joinRoutePrefix(destinationPrefix, source),
78
+ source,
79
+ }));
80
+ }
81
+ async function resolveExistingRewrites(rewrites) {
82
+ return await rewrites?.();
83
+ }
84
+ function mergeRewriteRules(existing, ashRules) {
85
+ if (existing === undefined) {
86
+ return ashRules;
87
+ }
88
+ if (!isRewriteSections(existing)) {
89
+ return [...ashRules, ...existing];
90
+ }
91
+ return {
92
+ ...existing,
93
+ beforeFiles: [...ashRules, ...(existing.beforeFiles ?? [])],
94
+ };
95
+ }
96
+ function isRewriteSections(rewrites) {
97
+ return !Array.isArray(rewrites);
98
+ }
99
+ async function resolveNextConfig(configOrFunction, phase, context) {
100
+ return typeof configOrFunction === "function"
101
+ ? await configOrFunction(phase, context)
102
+ : configOrFunction;
103
+ }
104
+ /**
105
+ * Wraps a Next.js config so same-origin Ash endpoints proxy to a separate Ash
106
+ * service.
107
+ *
108
+ * In development, `withAsh` starts `ash dev --no-repl --port 0` for the Ash
109
+ * app and rewrites Ash protocol endpoints to that local URL. In production on
110
+ * Vercel, it rewrites to the private Ash service prefix configured in
111
+ * `vercel.json` using the deployment origin from `VERCEL_URL`. Outside Vercel
112
+ * production, it serves an existing `.output/server/index.mjs` Ash build on a
113
+ * stable local port when present; otherwise set `ASH_NEXT_PRODUCTION_ORIGIN` to
114
+ * the origin that serves the Ash service namespace.
115
+ */
116
+ export function withAsh(configOrFunction, options = {}) {
117
+ const nextRoot = process.cwd();
118
+ const appRoot = resolveApplicationRoot(options.ashRoot);
119
+ const servicePrefixInput = normalizeRoutePrefix(options.servicePrefix ?? ASH_NEXT_SERVICE_PREFIX);
120
+ const shouldConfigureVercelJson = options.configureVercelJson !== false;
121
+ return async function ashNextConfig(phase, context) {
122
+ const nextConfig = await resolveNextConfig(configOrFunction, phase, context);
123
+ const existingRewrites = nextConfig.rewrites;
124
+ const configuredVercel = shouldConfigureVercelJson
125
+ ? await ensureAshVercelJson({
126
+ appRoot,
127
+ nextRoot,
128
+ servicePrefix: servicePrefixInput,
129
+ })
130
+ : {
131
+ servicePrefix: servicePrefixInput,
132
+ };
133
+ const productionDestination = resolveProductionDestination(configuredVercel.servicePrefix);
134
+ return {
135
+ ...nextConfig,
136
+ async rewrites() {
137
+ const [existing, destinationPrefix] = await Promise.all([
138
+ resolveExistingRewrites(existingRewrites),
139
+ resolveAshDestinationPrefix({
140
+ appRoot,
141
+ phase,
142
+ productionDestinationPrefix: productionDestination.destinationPrefix,
143
+ productionServerOrigin: productionDestination.localServerOrigin,
144
+ }),
145
+ ]);
146
+ return mergeRewriteRules(existing, createAshRewriteRules(destinationPrefix));
147
+ },
148
+ };
149
+ };
150
+ }
@@ -0,0 +1,6 @@
1
+ export declare function resolveAshDestinationPrefix(input: {
2
+ readonly appRoot: string;
3
+ readonly phase: string;
4
+ readonly productionDestinationPrefix: string;
5
+ readonly productionServerOrigin?: string;
6
+ }): Promise<string>;
@@ -0,0 +1,348 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, open, readFile, rm, stat, writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { resolvePackageRoot } from "#internal/application/package.js";
6
+ import { ASH_ROUTE_PREFIX } from "#protocol/routes.js";
7
+ const ASH_BASE_URL_ENV = "ASH_BASE_URL";
8
+ const DEFAULT_SERVER_READY_TIMEOUT_MS = 30_000;
9
+ const DEV_SERVER_REGISTRY_TIMEOUT_MS = 30_000;
10
+ const DEV_SERVER_REGISTRY_POLL_MS = 100;
11
+ const DEV_SERVER_STALE_LOCK_MS = 30_000;
12
+ const ASH_CACHE_DIRECTORY_NAME = ".ash";
13
+ const ASH_NEXT_DEV_SERVER_FILE_NAME = "next-dev-server.json";
14
+ const ASH_NEXT_DEV_SERVER_LOCK_FILE_NAME = "next-dev-server.lock";
15
+ const LOCAL_SERVER_URL_PATTERN = /https?:\/\/(?:\[[^\]\s]+\]|[^\s/:[\]]+)(?::\d+)?/;
16
+ const NEXT_PHASE_PRODUCTION_BUILD = "phase-production-build";
17
+ const globalStateSymbol = Symbol.for("experimental-ash.next.state");
18
+ function getGlobalState() {
19
+ const globalWithState = globalThis;
20
+ globalWithState[globalStateSymbol] ??= {
21
+ servers: new Map(),
22
+ };
23
+ return globalWithState[globalStateSymbol];
24
+ }
25
+ function joinRoutePrefix(prefix, path) {
26
+ return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
27
+ }
28
+ function normalizeOrigin(origin) {
29
+ return new URL(origin).origin;
30
+ }
31
+ function readAshBaseUrlEnvironment() {
32
+ const configuredUrl = process.env[ASH_BASE_URL_ENV];
33
+ if (configuredUrl === undefined || configuredUrl.trim().length === 0) {
34
+ return undefined;
35
+ }
36
+ return normalizeOrigin(configuredUrl);
37
+ }
38
+ function isNodeErrorWithCode(error, code) {
39
+ return error instanceof Error && "code" in error && error.code === code;
40
+ }
41
+ function delay(ms) {
42
+ return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
43
+ }
44
+ function isRecord(value) {
45
+ return value !== null && typeof value === "object" && !Array.isArray(value);
46
+ }
47
+ function resolveAshCacheDirectory(appRoot) {
48
+ return join(appRoot, ASH_CACHE_DIRECTORY_NAME);
49
+ }
50
+ function resolveAshDevServerRegistryPath(appRoot) {
51
+ return join(resolveAshCacheDirectory(appRoot), ASH_NEXT_DEV_SERVER_FILE_NAME);
52
+ }
53
+ function resolveAshDevServerLockPath(appRoot) {
54
+ return join(resolveAshCacheDirectory(appRoot), ASH_NEXT_DEV_SERVER_LOCK_FILE_NAME);
55
+ }
56
+ function normalizeDevServerRegistry(value) {
57
+ if (!isRecord(value)) {
58
+ return undefined;
59
+ }
60
+ if (typeof value.appRoot !== "string" ||
61
+ typeof value.origin !== "string" ||
62
+ typeof value.updatedAt !== "string") {
63
+ return undefined;
64
+ }
65
+ if (value.pid !== null && typeof value.pid !== "number") {
66
+ return undefined;
67
+ }
68
+ try {
69
+ return {
70
+ appRoot: value.appRoot,
71
+ origin: normalizeOrigin(value.origin),
72
+ pid: value.pid,
73
+ updatedAt: value.updatedAt,
74
+ };
75
+ }
76
+ catch {
77
+ return undefined;
78
+ }
79
+ }
80
+ async function isAshServerHealthy(origin) {
81
+ const controller = new AbortController();
82
+ const timeout = setTimeout(() => {
83
+ controller.abort();
84
+ }, 1_000);
85
+ try {
86
+ const response = await fetch(joinRoutePrefix(origin, `${ASH_ROUTE_PREFIX}/health`), {
87
+ signal: controller.signal,
88
+ });
89
+ return response.ok;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ finally {
95
+ clearTimeout(timeout);
96
+ }
97
+ }
98
+ async function readUsableAshDevServerRegistry(appRoot) {
99
+ try {
100
+ const registry = normalizeDevServerRegistry(JSON.parse(await readFile(resolveAshDevServerRegistryPath(appRoot), "utf8")));
101
+ if (registry === undefined || registry.appRoot !== appRoot) {
102
+ return undefined;
103
+ }
104
+ if (!(await isAshServerHealthy(registry.origin))) {
105
+ return undefined;
106
+ }
107
+ process.env[ASH_BASE_URL_ENV] = registry.origin;
108
+ return registry.origin;
109
+ }
110
+ catch (error) {
111
+ if (isNodeErrorWithCode(error, "ENOENT")) {
112
+ return undefined;
113
+ }
114
+ throw error;
115
+ }
116
+ }
117
+ async function writeAshDevServerRegistry(appRoot, handle) {
118
+ await mkdir(resolveAshCacheDirectory(appRoot), {
119
+ recursive: true,
120
+ });
121
+ await writeFile(resolveAshDevServerRegistryPath(appRoot), `${JSON.stringify({
122
+ appRoot,
123
+ origin: handle.origin,
124
+ pid: handle.process?.pid ?? null,
125
+ updatedAt: new Date().toISOString(),
126
+ }, null, 2)}\n`);
127
+ }
128
+ async function removeStaleAshDevServerLock(lockPath) {
129
+ try {
130
+ const lockStat = await stat(lockPath);
131
+ if (Date.now() - lockStat.mtimeMs > DEV_SERVER_STALE_LOCK_MS) {
132
+ await rm(lockPath, {
133
+ force: true,
134
+ });
135
+ }
136
+ }
137
+ catch (error) {
138
+ if (!isNodeErrorWithCode(error, "ENOENT")) {
139
+ throw error;
140
+ }
141
+ }
142
+ }
143
+ async function acquireAshDevServerLock(appRoot) {
144
+ const cacheDirectory = resolveAshCacheDirectory(appRoot);
145
+ const lockPath = resolveAshDevServerLockPath(appRoot);
146
+ const deadline = Date.now() + DEV_SERVER_REGISTRY_TIMEOUT_MS;
147
+ await mkdir(cacheDirectory, {
148
+ recursive: true,
149
+ });
150
+ while (true) {
151
+ try {
152
+ const lockFile = await open(lockPath, "wx");
153
+ await lockFile.writeFile(`${String(process.pid)}\n`);
154
+ await lockFile.close();
155
+ return async () => {
156
+ await rm(lockPath, {
157
+ force: true,
158
+ });
159
+ };
160
+ }
161
+ catch (error) {
162
+ if (!isNodeErrorWithCode(error, "EEXIST")) {
163
+ throw error;
164
+ }
165
+ const registeredOrigin = await readUsableAshDevServerRegistry(appRoot);
166
+ if (registeredOrigin !== undefined) {
167
+ return async () => { };
168
+ }
169
+ await removeStaleAshDevServerLock(lockPath);
170
+ if (Date.now() > deadline) {
171
+ throw new Error(`Timed out after ${DEV_SERVER_REGISTRY_TIMEOUT_MS}ms waiting for another Next.js process to start Ash.`);
172
+ }
173
+ await delay(DEV_SERVER_REGISTRY_POLL_MS);
174
+ }
175
+ }
176
+ }
177
+ function createAshBinaryPath() {
178
+ return join(resolvePackageRoot(), "bin", "ash.js");
179
+ }
180
+ function normalizeParsedOrigin(urlText) {
181
+ const url = new URL(urlText);
182
+ return url.origin;
183
+ }
184
+ function startServerProcess(input) {
185
+ return new Promise((resolvePromise, reject) => {
186
+ const child = spawn(input.command, input.args, {
187
+ cwd: input.cwd,
188
+ env: {
189
+ ...process.env,
190
+ ...input.env,
191
+ },
192
+ stdio: ["ignore", "pipe", "pipe"],
193
+ });
194
+ const timeout = setTimeout(() => {
195
+ child.kill();
196
+ reject(new Error(`Timed out after ${input.timeoutMs ?? DEFAULT_SERVER_READY_TIMEOUT_MS}ms waiting for Ash to print its server URL.`));
197
+ }, input.timeoutMs ?? DEFAULT_SERVER_READY_TIMEOUT_MS);
198
+ const cleanup = () => {
199
+ clearTimeout(timeout);
200
+ child.off("error", handleError);
201
+ child.off("exit", handleEarlyExit);
202
+ };
203
+ const handleError = (error) => {
204
+ cleanup();
205
+ reject(error);
206
+ };
207
+ const handleEarlyExit = (code, signal) => {
208
+ cleanup();
209
+ reject(new Error(`Ash server process exited before printing its server URL (code ${String(code)}, signal ${String(signal)}).`));
210
+ };
211
+ const handleOutput = (chunk) => {
212
+ const match = LOCAL_SERVER_URL_PATTERN.exec(chunk.toString("utf8"));
213
+ if (match === null) {
214
+ return;
215
+ }
216
+ cleanup();
217
+ resolvePromise({
218
+ origin: normalizeParsedOrigin(match[0]),
219
+ process: child,
220
+ });
221
+ };
222
+ const handleStdout = (chunk) => {
223
+ process.stdout.write(chunk);
224
+ handleOutput(chunk);
225
+ };
226
+ const handleStderr = (chunk) => {
227
+ process.stderr.write(chunk);
228
+ handleOutput(chunk);
229
+ };
230
+ child.once("error", handleError);
231
+ child.once("exit", handleEarlyExit);
232
+ child.stdout.on("data", handleStdout);
233
+ child.stderr.on("data", handleStderr);
234
+ });
235
+ }
236
+ function installProcessShutdown(handle) {
237
+ const childProcess = handle.process;
238
+ if (childProcess === undefined) {
239
+ return handle;
240
+ }
241
+ const close = () => {
242
+ if (!childProcess.killed) {
243
+ childProcess.kill();
244
+ }
245
+ };
246
+ process.once("beforeExit", close);
247
+ process.once("exit", close);
248
+ return handle;
249
+ }
250
+ function startAshDevServer(appRoot) {
251
+ return startServerProcess({
252
+ args: [createAshBinaryPath(), "dev", "--no-repl", "--port", "0"],
253
+ command: process.execPath,
254
+ cwd: appRoot,
255
+ }).then((handle) => {
256
+ process.env[ASH_BASE_URL_ENV] = handle.origin;
257
+ return installProcessShutdown(handle);
258
+ });
259
+ }
260
+ function startAshProductionServer(input) {
261
+ const parsedOrigin = new URL(input.origin);
262
+ const port = parsedOrigin.port;
263
+ const serverEntry = join(input.appRoot, ".output", "server", "index.mjs");
264
+ if (!existsSync(serverEntry)) {
265
+ return undefined;
266
+ }
267
+ return startServerProcess({
268
+ args: [serverEntry],
269
+ command: process.execPath,
270
+ cwd: input.appRoot,
271
+ env: {
272
+ HOST: parsedOrigin.hostname,
273
+ NITRO_HOST: parsedOrigin.hostname,
274
+ NITRO_PORT: port,
275
+ PORT: port,
276
+ },
277
+ }).then(installProcessShutdown);
278
+ }
279
+ async function resolveSharedAshDevServer(appRoot) {
280
+ const registeredOrigin = await readUsableAshDevServerRegistry(appRoot);
281
+ if (registeredOrigin !== undefined) {
282
+ return {
283
+ origin: registeredOrigin,
284
+ };
285
+ }
286
+ const releaseLock = await acquireAshDevServerLock(appRoot);
287
+ try {
288
+ const lockedRegisteredOrigin = await readUsableAshDevServerRegistry(appRoot);
289
+ if (lockedRegisteredOrigin !== undefined) {
290
+ return {
291
+ origin: lockedRegisteredOrigin,
292
+ };
293
+ }
294
+ const handle = await startAshDevServer(appRoot);
295
+ await writeAshDevServerRegistry(appRoot, handle);
296
+ return handle;
297
+ }
298
+ finally {
299
+ await releaseLock();
300
+ }
301
+ }
302
+ export async function resolveAshDestinationPrefix(input) {
303
+ const state = getGlobalState();
304
+ if (process.env.NODE_ENV === "production") {
305
+ if (input.phase === NEXT_PHASE_PRODUCTION_BUILD) {
306
+ return input.productionDestinationPrefix;
307
+ }
308
+ const key = `production:${input.appRoot}`;
309
+ let productionServer = state.servers.get(key);
310
+ if (productionServer === undefined) {
311
+ productionServer =
312
+ process.env.VERCEL || input.productionServerOrigin === undefined
313
+ ? undefined
314
+ : startAshProductionServer({
315
+ appRoot: input.appRoot,
316
+ origin: input.productionServerOrigin,
317
+ });
318
+ if (productionServer !== undefined) {
319
+ productionServer = productionServer.catch((error) => {
320
+ state.servers.delete(key);
321
+ throw error;
322
+ });
323
+ state.servers.set(key, productionServer);
324
+ }
325
+ }
326
+ if (productionServer !== undefined) {
327
+ return (await productionServer).origin;
328
+ }
329
+ return input.productionDestinationPrefix;
330
+ }
331
+ const configuredAshBaseUrl = readAshBaseUrlEnvironment();
332
+ if (configuredAshBaseUrl !== undefined) {
333
+ return configuredAshBaseUrl;
334
+ }
335
+ if (process.env.NODE_ENV !== "development") {
336
+ return input.productionDestinationPrefix;
337
+ }
338
+ const key = `dev:${input.appRoot}`;
339
+ let server = state.servers.get(key);
340
+ if (server === undefined) {
341
+ server = resolveSharedAshDevServer(input.appRoot).catch((error) => {
342
+ state.servers.delete(key);
343
+ throw error;
344
+ });
345
+ state.servers.set(key, server);
346
+ }
347
+ return (await server).origin;
348
+ }
@@ -0,0 +1,8 @@
1
+ export interface EnsureVercelJsonResult {
2
+ readonly servicePrefix: string;
3
+ }
4
+ export declare function ensureAshVercelJson(input: {
5
+ readonly appRoot: string;
6
+ readonly nextRoot: string;
7
+ readonly servicePrefix: string;
8
+ }): Promise<EnsureVercelJsonResult>;
@@ -0,0 +1,76 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
+ const VERCEL_JSON_FILE_NAME = "vercel.json";
4
+ const VERCEL_JSON_SCHEMA = "https://openapi.vercel.sh/vercel.json";
5
+ function isRecord(value) {
6
+ return value !== null && typeof value === "object" && !Array.isArray(value);
7
+ }
8
+ function resolveRelativeEntrypoint(fromRoot, toRoot) {
9
+ const relativePath = relative(fromRoot, toRoot);
10
+ if (relativePath.length === 0) {
11
+ return ".";
12
+ }
13
+ return relativePath.replaceAll("\\", "/");
14
+ }
15
+ function normalizeVercelJsonConfig(value) {
16
+ if (!isRecord(value)) {
17
+ throw new Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);
18
+ }
19
+ const experimentalServices = value.experimentalServices;
20
+ if (experimentalServices !== undefined && !isRecord(experimentalServices)) {
21
+ throw new Error(`${VERCEL_JSON_FILE_NAME} experimentalServices must be a JSON object.`);
22
+ }
23
+ return value;
24
+ }
25
+ async function readVercelJsonConfig(path) {
26
+ try {
27
+ return normalizeVercelJsonConfig(JSON.parse(await readFile(path, "utf8")));
28
+ }
29
+ catch (error) {
30
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
31
+ return {};
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+ function findServiceByFramework(services, framework) {
37
+ return Object.values(services).find((service) => service.framework === framework);
38
+ }
39
+ export async function ensureAshVercelJson(input) {
40
+ const vercelJsonPath = join(input.nextRoot, VERCEL_JSON_FILE_NAME);
41
+ const existingConfig = await readVercelJsonConfig(vercelJsonPath);
42
+ const nextEntrypoint = ".";
43
+ const ashEntrypoint = resolveRelativeEntrypoint(input.nextRoot, input.appRoot);
44
+ const existingServices = existingConfig.experimentalServices ?? {};
45
+ const configuredAshService = findServiceByFramework(existingServices, "ash");
46
+ const configuredNextService = findServiceByFramework(existingServices, "nextjs");
47
+ const servicePrefix = configuredAshService?.routePrefix ?? input.servicePrefix;
48
+ const experimentalServices = {
49
+ ...existingServices,
50
+ };
51
+ if (configuredNextService === undefined) {
52
+ experimentalServices.web = {
53
+ entrypoint: nextEntrypoint,
54
+ framework: "nextjs",
55
+ routePrefix: "/",
56
+ };
57
+ }
58
+ if (configuredAshService === undefined) {
59
+ experimentalServices.ash = {
60
+ entrypoint: ashEntrypoint,
61
+ framework: "ash",
62
+ routePrefix: servicePrefix,
63
+ };
64
+ }
65
+ const vercelConfig = {
66
+ ...existingConfig,
67
+ $schema: existingConfig.$schema ?? VERCEL_JSON_SCHEMA,
68
+ experimentalServices,
69
+ };
70
+ if (JSON.stringify(existingConfig) !== JSON.stringify(vercelConfig)) {
71
+ await writeFile(vercelJsonPath, `${JSON.stringify(vercelConfig, null, 2)}\n`);
72
+ }
73
+ return {
74
+ servicePrefix,
75
+ };
76
+ }
@@ -3,4 +3,4 @@
3
3
  */
4
4
  export { getSkill } from "#context/accessors.js";
5
5
  export type { SkillFile, SkillHandle } from "#execution/skills/types.js";
6
- export { defineSkill, type SkillDefinition } from "#public/definitions/skill.js";
6
+ export { defineSkill, type NamedSkillDefinition, type SkillDefinition, type SkillFileContent, type SkillPackageDefinition, } from "#public/definitions/skill.js";
@@ -2,4 +2,4 @@
2
2
  * Skill authoring helpers and runtime accessors.
3
3
  */
4
4
  export { getSkill } from "#context/accessors.js";
5
- export { defineSkill } from "#public/definitions/skill.js";
5
+ export { defineSkill, } from "#public/definitions/skill.js";
@@ -0,0 +1,68 @@
1
+ import type { RuntimeActionResult } from "#runtime/actions/types.js";
2
+ import type { McpClientConnectionDefinition } from "#public/definitions/connections/mcp.js";
3
+ import type { ToolDefinition } from "#public/definitions/tool.js";
4
+ /**
5
+ * Narrowed tool result returned by {@link toolResultFrom} when the
6
+ * action result matches an authored {@link ToolDefinition}.
7
+ *
8
+ * `TOutput` is inferred from the tool definition's `execute` return type.
9
+ */
10
+ export interface MatchedToolResult<TOutput> {
11
+ readonly callId: string;
12
+ readonly output: TOutput;
13
+ readonly toolName: string;
14
+ }
15
+ /**
16
+ * Narrowed tool result returned by {@link toolResultFrom} when the
17
+ * action result matches an MCP connection.
18
+ *
19
+ * `output` stays `unknown` because MCP tool schemas are remote.
20
+ * `connectionToolName` is the unqualified MCP tool name (e.g.
21
+ * `"list_issues"`) while `toolName` is the full qualified name
22
+ * (e.g. `"linear__list_issues"`).
23
+ */
24
+ export interface MatchedConnectionResult {
25
+ readonly callId: string;
26
+ readonly connectionToolName: string;
27
+ readonly output: unknown;
28
+ readonly toolName: string;
29
+ }
30
+ /**
31
+ * Discriminated source entry stamped on authored definitions during
32
+ * agent resolution via {@link stampDefinitionSource}.
33
+ */
34
+ export type DefinitionSourceEntry = {
35
+ readonly kind: "connection";
36
+ readonly name: string;
37
+ } | {
38
+ readonly kind: "tool";
39
+ readonly name: string;
40
+ };
41
+ /**
42
+ * Stamps the path-derived runtime name onto an authored definition
43
+ * object as a non-enumerable symbol property.
44
+ *
45
+ * Called by the resolution pipeline (`resolveToolDefinition`,
46
+ * `resolveConnectionDefinition`) so {@link toolResultFrom} can read
47
+ * the name back without a separate lookup structure.
48
+ */
49
+ export declare function stampDefinitionSource(definition: object, entry: DefinitionSourceEntry): void;
50
+ /**
51
+ * Overloaded signature for {@link toolResultFrom}.
52
+ */
53
+ export interface ToolResultFromFn {
54
+ <TInput, TOutput>(result: RuntimeActionResult, tool: ToolDefinition<TInput, TOutput>): MatchedToolResult<TOutput> | undefined;
55
+ (result: RuntimeActionResult, connection: McpClientConnectionDefinition): MatchedConnectionResult | undefined;
56
+ }
57
+ /**
58
+ * Narrows a {@link RuntimeActionResult} to a typed tool or connection
59
+ * result by matching against an authored definition object.
60
+ *
61
+ * Pass a `ToolDefinition` to get a typed `output`; pass a
62
+ * `McpClientConnectionDefinition` to match any tool from that
63
+ * connection (`output` stays `unknown`).
64
+ *
65
+ * Returns `undefined` when the result doesn't match, or when
66
+ * `isError` is `true`.
67
+ */
68
+ export declare const toolResultFrom: ToolResultFromFn;