experimental-ash 0.61.0 → 0.63.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 +72 -0
- package/dist/docs/public/advanced/hooks.mdx +8 -2
- package/dist/docs/public/advanced/runs-and-streaming.md +6 -3
- package/dist/docs/public/advanced/typescript-api.md +32 -0
- package/dist/docs/public/channels/custom.mdx +23 -0
- package/dist/docs/public/frontend/README.md +8 -4
- package/dist/docs/public/frontend/meta.json +9 -1
- package/dist/docs/public/frontend/nextjs.md +4 -4
- package/dist/docs/public/frontend/nuxt.md +168 -0
- package/dist/docs/public/frontend/sveltekit.md +177 -0
- package/dist/docs/public/frontend/use-ash-agent-svelte.md +185 -0
- package/dist/docs/public/frontend/use-ash-agent-vue.md +236 -0
- package/dist/docs/public/frontend/use-ash-agent.md +14 -14
- package/dist/docs/public/getting-started.mdx +2 -0
- package/dist/skills/ash-add-agent/SKILL.md +29 -17
- package/dist/skills/ash-add-next/SKILL.md +58 -8
- package/dist/src/channel/websocket-upgrade-server.d.ts +26 -0
- package/dist/src/channel/websocket-upgrade-server.js +1 -0
- package/dist/src/chunks/use-ash-agent-BQJLh7KU.js +1224 -0
- package/dist/src/chunks/use-ash-agent-CRWVA4i-.js +1192 -0
- package/dist/src/client/ash-agent-store.d.ts +61 -0
- package/dist/src/client/ash-agent-store.js +2 -0
- package/dist/src/client/index.d.ts +2 -0
- package/dist/src/client/index.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +9 -9
- package/dist/src/compiled/@ai-sdk/anthropic/_provider-utils.d.ts +1 -1
- package/dist/src/compiled/@ai-sdk/anthropic/index.d.ts +3 -3
- package/dist/src/compiled/@ai-sdk/anthropic/index.js +2 -2
- package/dist/src/compiled/@ai-sdk/google/index.d.ts +52 -2
- package/dist/src/compiled/@ai-sdk/google/index.js +6 -6
- package/dist/src/compiled/@ai-sdk/mcp/index.js +1 -1
- package/dist/src/compiled/@ai-sdk/openai/index.d.ts +32 -2
- package/dist/src/compiled/@ai-sdk/openai/index.js +2 -2
- package/dist/src/compiled/@ai-sdk/provider/index.d.ts +507 -1
- package/dist/src/compiled/@chat-adapter/slack/index.js +25 -25
- package/dist/src/compiled/@workflow/core/events-consumer.d.ts +8 -0
- package/dist/src/compiled/@workflow/core/index.js +2 -2
- package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +1 -0
- package/dist/src/compiled/@workflow/core/runtime.js +29 -29
- package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
- package/dist/src/compiled/@workflow/core/workflow.js +1 -1
- package/dist/src/compiled/@workflow/errors/error-codes.d.ts +2 -0
- package/dist/src/compiled/@workflow/errors/index.d.ts +14 -0
- package/dist/src/compiled/@workflow/errors/index.js +1 -1
- package/dist/src/compiled/@workflow/world/queue.d.ts +8 -0
- package/dist/src/compiled/_chunks/workflow/{dist-Chj-QcBs.js → dist-gEXVSMPU.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/dist-zpK2YVVA.js +3 -0
- package/dist/src/compiled/_chunks/workflow/resume-hook-BFK9mgsb.js +12 -0
- package/dist/src/compiled/_chunks/workflow/{sleep-Bg0t23kF.js → sleep-CeJckNg2.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{symbols-u476uwyR.js → symbols-BWCAoPHE.js} +1 -1
- package/dist/src/compiler/manifest.d.ts +12 -0
- package/dist/src/compiler/manifest.js +1 -1
- package/dist/src/compiler/normalize-connection.d.ts +10 -2
- package/dist/src/compiler/normalize-connection.js +1 -1
- package/dist/src/execution/sandbox/bindings/local.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
- package/dist/src/internal/application/package.d.ts +1 -0
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/connection.d.ts +9 -0
- package/dist/src/internal/authored-definition/connection.js +1 -1
- package/dist/src/internal/nitro/host/build-application.js +1 -1
- package/dist/src/internal/nitro/host/build-vercel-agent-summary.js +1 -1
- package/dist/src/internal/nitro/host/channel-routes.js +2 -2
- package/dist/src/internal/vercel-agent-summary.d.ts +6 -4
- package/dist/src/internal/workflow-bundle/ash-service-route-output.js +11 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
- package/dist/src/public/channels/auth.d.ts +1 -1
- package/dist/src/public/channels/index.d.ts +1 -0
- package/dist/src/public/channels/index.js +1 -1
- package/dist/src/public/connections/index.d.ts +3 -2
- package/dist/src/public/connections/index.js +1 -1
- package/dist/src/public/definitions/connections/mcp.d.ts +4 -12
- package/dist/src/public/definitions/connections/mcp.js +1 -1
- package/dist/src/public/definitions/connections/openapi.d.ts +100 -0
- package/dist/src/public/definitions/connections/openapi.js +1 -0
- package/dist/src/public/definitions/connections/protocol.d.ts +12 -0
- package/dist/src/public/definitions/connections/protocol.js +1 -0
- package/dist/src/public/next/index.d.ts +6 -6
- package/dist/src/public/next/index.js +1 -1
- package/dist/src/public/next/{vercel-json.d.ts → vercel-output-config.d.ts} +3 -3
- package/dist/src/public/next/vercel-output-config.js +1 -0
- package/dist/src/public/nuxt/dev-server.d.ts +24 -0
- package/dist/src/public/nuxt/dev-server.js +1 -0
- package/dist/src/public/nuxt/index.d.ts +1 -0
- package/dist/src/public/nuxt/index.js +1 -0
- package/dist/src/public/nuxt/module.d.ts +31 -0
- package/dist/src/public/nuxt/module.js +1 -0
- package/dist/src/public/nuxt/routing.d.ts +55 -0
- package/dist/src/public/nuxt/routing.js +1 -0
- package/dist/src/public/nuxt/vercel-json.d.ts +17 -0
- package/dist/src/public/{next → nuxt}/vercel-json.js +1 -1
- package/dist/src/public/sveltekit/dev-server.d.ts +24 -0
- package/dist/src/public/sveltekit/dev-server.js +1 -0
- package/dist/src/public/sveltekit/index.d.ts +39 -0
- package/dist/src/public/sveltekit/index.js +1 -0
- package/dist/src/public/sveltekit/routing.d.ts +32 -0
- package/dist/src/public/sveltekit/routing.js +1 -0
- package/dist/src/public/sveltekit/vercel-json.d.ts +17 -0
- package/dist/src/public/sveltekit/vercel-json.js +1 -0
- package/dist/src/react/use-ash-agent.d.ts +5 -27
- package/dist/src/react/use-ash-agent.js +1 -2
- package/dist/src/runtime/connections/openapi-client.d.ts +43 -0
- package/dist/src/runtime/connections/openapi-client.js +1 -0
- package/dist/src/runtime/connections/openapi-operations.d.ts +30 -0
- package/dist/src/runtime/connections/openapi-operations.js +1 -0
- package/dist/src/runtime/connections/openapi-schema.d.ts +39 -0
- package/dist/src/runtime/connections/openapi-schema.js +1 -0
- package/dist/src/runtime/connections/openapi-security.d.ts +41 -0
- package/dist/src/runtime/connections/openapi-security.js +1 -0
- package/dist/src/runtime/connections/openapi-spec.d.ts +20 -0
- package/dist/src/runtime/connections/openapi-spec.js +1 -0
- package/dist/src/runtime/connections/registry.d.ts +5 -7
- package/dist/src/runtime/connections/registry.js +1 -1
- package/dist/src/runtime/connections/types.d.ts +23 -0
- package/dist/src/runtime/resolve-connection.js +1 -1
- package/dist/src/runtime/types.d.ts +15 -1
- package/dist/src/shared/sandbox-session.d.ts +1 -1
- package/dist/src/shared/vercel-output-directory.d.ts +2 -0
- package/dist/src/shared/vercel-output-directory.js +1 -0
- package/dist/src/svelte/index.d.ts +3 -0
- package/dist/src/svelte/index.js +3 -0
- package/dist/src/svelte/use-ash-agent.d.ts +80 -0
- package/dist/src/svelte/use-ash-agent.js +3 -0
- package/dist/src/vue/index.d.ts +3 -0
- package/dist/src/vue/index.js +3 -0
- package/dist/src/vue/use-ash-agent.d.ts +78 -0
- package/dist/src/vue/use-ash-agent.js +3 -0
- package/package.json +59 -14
- package/dist/src/compiled/_chunks/workflow/dist-C4EHshZE.js +0 -3
- package/dist/src/compiled/_chunks/workflow/resume-hook-BlALLgSA.js +0 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const PROTOCOL_KEY=Symbol.for(`experimental-ash.connection-protocol`);function stampConnectionProtocol(e,t){Object.defineProperty(e,PROTOCOL_KEY,{configurable:!0,value:t})}function readConnectionProtocol(e){if(typeof e==`object`&&e&&PROTOCOL_KEY in e){let t=e[PROTOCOL_KEY];if(t!==void 0)return t}return`mcp`}export{readConnectionProtocol,stampConnectionProtocol};
|
|
@@ -53,15 +53,15 @@ export interface WithAshOptions {
|
|
|
53
53
|
*/
|
|
54
54
|
readonly ashBuildCommand?: string;
|
|
55
55
|
/**
|
|
56
|
-
* Set to `false` to skip creating or updating
|
|
56
|
+
* Set to `false` to skip creating or updating Vercel Build Output config.
|
|
57
57
|
*
|
|
58
|
-
* By default `withAsh` ensures
|
|
59
|
-
* for the Next.js app and Ash app.
|
|
58
|
+
* By default `withAsh` ensures `.vercel/output/config.json` contains
|
|
59
|
+
* `experimentalServices` for the Next.js app and Ash app.
|
|
60
60
|
*/
|
|
61
|
-
readonly
|
|
61
|
+
readonly configureVercelOutput?: boolean;
|
|
62
62
|
/**
|
|
63
63
|
* Private Vercel service prefix for the Ash deployment. This must match the
|
|
64
|
-
* Ash service's
|
|
64
|
+
* Ash service's mount in Vercel Build Output config.
|
|
65
65
|
*/
|
|
66
66
|
readonly servicePrefix?: string;
|
|
67
67
|
}
|
|
@@ -72,7 +72,7 @@ export interface WithAshOptions {
|
|
|
72
72
|
* In development, `withAsh` starts `ash dev --no-repl --port 0` for the Ash
|
|
73
73
|
* app and rewrites Ash protocol endpoints to that local URL. In production on
|
|
74
74
|
* Vercel, it rewrites to the private Ash service prefix configured in
|
|
75
|
-
*
|
|
75
|
+
* `.vercel/output/config.json`. Outside Vercel production, it serves an existing
|
|
76
76
|
* `.output/server/index.mjs` Ash build on a stable local port when present;
|
|
77
77
|
* otherwise set `ASH_NEXT_PRODUCTION_ORIGIN` to the origin that serves the Ash
|
|
78
78
|
* service namespace.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveAshDestinationPrefix}from"./server.js";import{
|
|
1
|
+
import{resolveAshDestinationPrefix}from"./server.js";import{ensureAshVercelOutputConfig}from"./vercel-output-config.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{isAbsolute,resolve}from"node:path";const ASH_NEXT_SERVICE_PREFIX=`/_ash_internal/ash`,ASH_NEXT_PRODUCTION_PORT_ENV=`ASH_NEXT_PRODUCTION_PORT`,ASH_PROXY_REWRITE_SOURCES=[`${ASH_ROUTE_PREFIX}/:path+`];function resolveApplicationRoot(e){return e===void 0||e.length===0?process.cwd():isAbsolute(e)?e:resolve(process.cwd(),e)}function normalizeRoutePrefix(e){let t=(e.startsWith(`/`)?e:`/${e}`).replace(/\/+$/,``);if(t.length===0)throw Error(`Ash Next.js service prefix cannot resolve to the root route.`);return t}function joinRoutePrefix(e,t){return`${e.replace(/\/+$/,``)}/${t.replace(/^\/+/,``)}`}function normalizeOrigin(e){return new URL(e.trim()).origin}function readLocalProductionPort(){let e=process.env[ASH_NEXT_PRODUCTION_PORT_ENV];if(e===void 0||e.trim().length===0)return 4274;let t=Number.parseInt(e,10);if(String(t)!==e.trim()||t<1||t>65535)throw Error(`${ASH_NEXT_PRODUCTION_PORT_ENV} must be an integer between 1 and 65535.`);return t}function resolveProductionDestination(e){if(process.env.VERCEL)return{destinationPrefix:e};let t=process.env.ASH_NEXT_PRODUCTION_ORIGIN;if(t!==void 0&&t.trim().length>0)return{destinationPrefix:joinRoutePrefix(normalizeOrigin(t),e)};let n=`http://127.0.0.1:${String(readLocalProductionPort())}`;return{destinationPrefix:n,localServerOrigin:n}}function createAshRewriteRules(e){return ASH_PROXY_REWRITE_SOURCES.map(t=>({destination:joinRoutePrefix(e,t),source:t}))}async function resolveExistingRewrites(e){return await e?.()}function mergeRewriteRules(e,t){return e===void 0?{beforeFiles:t}:isRewriteSections(e)?{...e,beforeFiles:[...t,...e.beforeFiles??[]]}:{afterFiles:e,beforeFiles:t}}function isRewriteSections(e){return!Array.isArray(e)}async function resolveNextConfig(e,t,n){return typeof e==`function`?await e(t,n):e}function withAsh(n,r={}){let i=process.cwd(),a=resolveApplicationRoot(r.ashRoot),o=normalizeRoutePrefix(r.servicePrefix??`/_ash_internal/ash`),s=r.configureVercelOutput!==!1;return async function(c,l){let u=await resolveNextConfig(n,c,l),d=u.rewrites,f=resolveProductionDestination((s?await ensureAshVercelOutputConfig({appRoot:a,ashBuildCommand:r.ashBuildCommand??`ash build`,nextRoot:i,servicePrefix:o}):{servicePrefix:o}).servicePrefix);return{...u,async rewrites(){let[t,n]=await Promise.all([resolveExistingRewrites(d),resolveAshDestinationPrefix({appRoot:a,phase:c,productionDestinationPrefix:f.destinationPrefix,productionServerOrigin:f.localServerOrigin})]);return mergeRewriteRules(t,createAshRewriteRules(n))}}}}export{ASH_NEXT_SERVICE_PREFIX,withAsh};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface EnsureVercelOutputConfigResult {
|
|
2
2
|
readonly servicePrefix: string;
|
|
3
3
|
}
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function ensureAshVercelOutputConfig(input: {
|
|
5
5
|
readonly appRoot: string;
|
|
6
6
|
readonly ashBuildCommand: string;
|
|
7
7
|
readonly nextRoot: string;
|
|
8
8
|
readonly servicePrefix: string;
|
|
9
|
-
}): Promise<
|
|
9
|
+
}): Promise<EnsureVercelOutputConfigResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{dirname,join,relative}from"node:path";import{mkdir,readFile,writeFile}from"node:fs/promises";import{findClosestLinkedVercelDirectory,findClosestVercelOutputDirectory}from"#shared/vercel-output-directory.js";const VERCEL_JSON_FILE_NAME=`vercel.json`,VERCEL_OUTPUT_CONFIG_FILE_NAME=`.vercel/output/config.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function hasServices(e){return e!==void 0&&Object.keys(e).length>0}function resolveRelativeEntrypoint(e,t){let r=relative(e,t);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}async function resolveVercelOutputConfigLocation(n){let r=await findClosestLinkedVercelDirectory(n),i=r===void 0?n:dirname(r),a=await findClosestVercelOutputDirectory(n);return a===void 0?r===void 0?{outputConfigPath:join(n,VERCEL_OUTPUT_CONFIG_FILE_NAME),projectRoot:i}:{outputConfigPath:join(r,`output`,`config.json`),projectRoot:i}:{outputConfigPath:join(a,`config.json`),projectRoot:i}}function normalizeVercelServicesConfig(e,t){if(!isRecord(e))throw Error(`${t} must contain a JSON object.`);let n=e.experimentalServices;if(n!==void 0&&!isRecord(n))throw Error(`${t} experimentalServices must be a JSON object.`);return e}async function readVercelServicesConfig(e,t){try{return normalizeVercelServicesConfig(JSON.parse(await readFile(e,`utf8`)),t)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}function resolveServicePrefix(e){if(e!==void 0){if(typeof e.routePrefix==`string`&&e.routePrefix.trim().length>0)return e.routePrefix.trim();if(typeof e.mount==`string`&&e.mount.trim().length>0)return e.mount.trim();if(isRecord(e.mount)&&typeof e.mount.path==`string`&&e.mount.path.trim().length>0)return e.mount.path.trim()}}function resolveConfiguredServicePrefix(e){return resolveServicePrefix(findServiceByFramework(e.services,`ash`))??e.servicePrefix}function assertRootServicesAreComplete(e){let t=findServiceByFramework(e.services,`ash`),n=findServiceByFramework(e.services,`nextjs`);if(t!==void 0&&n!==void 0)return resolveServicePrefix(t)??e.servicePrefix;throw Error(`${VERCEL_JSON_FILE_NAME} already defines experimentalServices, so withAsh cannot add generated services through ${VERCEL_OUTPUT_CONFIG_FILE_NAME}. Add both the Next.js and Ash services to ${VERCEL_JSON_FILE_NAME}, or remove experimentalServices from ${VERCEL_JSON_FILE_NAME}.`)}async function ensureAshVercelOutputConfig(n){let{outputConfigPath:i,projectRoot:o}=await resolveVercelOutputConfigLocation(n.nextRoot),s=(await readVercelServicesConfig(join(o,VERCEL_JSON_FILE_NAME),VERCEL_JSON_FILE_NAME)).experimentalServices;if(hasServices(s))return{servicePrefix:assertRootServicesAreComplete({services:s,servicePrefix:n.servicePrefix})};let c=await readVercelServicesConfig(i,VERCEL_OUTPUT_CONFIG_FILE_NAME),l=resolveRelativeEntrypoint(n.nextRoot,n.appRoot),u=c.experimentalServices??{},d=findServiceByFramework(u,`ash`),f=findServiceByFramework(u,`nextjs`),p=resolveConfiguredServicePrefix({services:u,servicePrefix:n.servicePrefix}),m={...u};f===void 0&&(m.web={entrypoint:`.`,framework:`nextjs`,mount:`/`,type:`web`}),d===void 0&&(m.ash={buildCommand:n.ashBuildCommand,entrypoint:l,framework:`ash`,mount:p,type:`web`});let h={...c,version:3,experimentalServices:m};return JSON.stringify(c)!==JSON.stringify(h)&&(await mkdir(dirname(i),{recursive:!0}),await writeFile(i,`${JSON.stringify(h,null,2)}\n`)),{servicePrefix:p}}export{ensureAshVercelOutputConfig};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
export declare const ASH_BASE_URL_ENV = "ASH_BASE_URL";
|
|
3
|
+
export interface AshProcessHandle {
|
|
4
|
+
readonly origin: string;
|
|
5
|
+
readonly process?: ChildProcess;
|
|
6
|
+
}
|
|
7
|
+
export interface AshDevServerRegistry {
|
|
8
|
+
readonly appRoot: string;
|
|
9
|
+
readonly origin: string;
|
|
10
|
+
readonly pid: number | null;
|
|
11
|
+
readonly updatedAt: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse and validate a persisted dev-server registry record. Returns
|
|
15
|
+
* `undefined` for anything that is not a well-formed registry so callers fall
|
|
16
|
+
* back to spawning a fresh server.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeDevServerRegistry(value: unknown): AshDevServerRegistry | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a shared Ash dev server for {@link appRoot}, reusing a healthy
|
|
21
|
+
* registered server when one exists and otherwise spawning a new one behind a
|
|
22
|
+
* cross-process lock so concurrent Nuxt processes don't each boot Ash.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveSharedAshDevServer(appRoot: string): Promise<AshProcessHandle>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{joinRoutePrefix,normalizeOrigin}from"./routing.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{join}from"node:path";import{mkdir,open,readFile,rm,stat,writeFile}from"node:fs/promises";import{spawn}from"node:child_process";import{resolvePackageRoot}from"#internal/application/package.js";const ASH_BASE_URL_ENV=`ASH_BASE_URL`,DEFAULT_SERVER_READY_TIMEOUT_MS=3e4,DEV_SERVER_REGISTRY_TIMEOUT_MS=3e4,LOCAL_SERVER_URL_PATTERN=/https?:\/\/(?:\[[^\]\s]+\]|[^\s/:[\]]+)(?::\d+)?/;function isNodeErrorWithCode(e,t){return e instanceof Error&&`code`in e&&e.code===t}function delay(e){return new Promise(t=>setTimeout(t,e))}function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveAshCacheDirectory(e){return join(e,`.ash`)}function resolveAshDevServerRegistryPath(e){return join(resolveAshCacheDirectory(e),`nuxt-dev-server.json`)}function resolveAshDevServerLockPath(e){return join(resolveAshCacheDirectory(e),`nuxt-dev-server.lock`)}function normalizeDevServerRegistry(e){if(isRecord(e)&&!(typeof e.appRoot!=`string`||typeof e.origin!=`string`||typeof e.updatedAt!=`string`)&&!(e.pid!==null&&typeof e.pid!=`number`))try{return{appRoot:e.appRoot,origin:normalizeOrigin(e.origin),pid:e.pid,updatedAt:e.updatedAt}}catch{return}}async function isAshServerHealthy(t){let r=new AbortController,i=setTimeout(()=>r.abort(),1e3);try{return(await fetch(joinRoutePrefix(t,`${ASH_ROUTE_PREFIX}/health`),{signal:r.signal})).ok}catch{return!1}finally{clearTimeout(i)}}async function readUsableAshDevServerRegistry(e){try{let t=normalizeDevServerRegistry(JSON.parse(await readFile(resolveAshDevServerRegistryPath(e),`utf8`)));return t===void 0||t.appRoot!==e||!await isAshServerHealthy(t.origin)?void 0:t.origin}catch(e){if(isNodeErrorWithCode(e,`ENOENT`))return;throw e}}async function writeAshDevServerRegistry(e,t){await mkdir(resolveAshCacheDirectory(e),{recursive:!0}),await writeFile(resolveAshDevServerRegistryPath(e),`${JSON.stringify({appRoot:e,origin:t.origin,pid:t.process?.pid??null,updatedAt:new Date().toISOString()},null,2)}\n`)}async function removeStaleAshDevServerLock(e){try{let t=await stat(e);Date.now()-t.mtimeMs>3e4&&await rm(e,{force:!0})}catch(e){if(!isNodeErrorWithCode(e,`ENOENT`))throw e}}async function acquireAshDevServerLock(e){let t=resolveAshCacheDirectory(e),n=resolveAshDevServerLockPath(e),r=Date.now()+DEV_SERVER_REGISTRY_TIMEOUT_MS;for(await mkdir(t,{recursive:!0});;)try{let e=await open(n,`wx`);return await e.writeFile(`${String(process.pid)}\n`),await e.close(),async()=>{await rm(n,{force:!0})}}catch(t){if(!isNodeErrorWithCode(t,`EEXIST`))throw t;if(await readUsableAshDevServerRegistry(e)!==void 0)return async()=>{};if(await removeStaleAshDevServerLock(n),Date.now()>r)throw Error(`Timed out after ${DEV_SERVER_REGISTRY_TIMEOUT_MS}ms waiting for another Nuxt process to start Ash.`);await delay(100)}}function createAshBinaryPath(){return join(resolvePackageRoot(),`bin`,`ash.js`)}function startServerProcess(e){return new Promise((n,r)=>{let i=spawn(e.command,e.args,{cwd:e.cwd,env:{...process.env,...e.env},stdio:[`ignore`,`pipe`,`pipe`]}),a=setTimeout(()=>{i.kill(),r(Error(`Timed out after ${DEFAULT_SERVER_READY_TIMEOUT_MS}ms waiting for Ash to print its server URL.`))},DEFAULT_SERVER_READY_TIMEOUT_MS),cleanup=()=>{clearTimeout(a),i.off(`error`,handleError),i.off(`exit`,handleEarlyExit)},handleError=e=>{cleanup(),r(e)},handleEarlyExit=(e,t)=>{cleanup(),r(Error(`Ash server process exited before printing its server URL (code ${String(e)}, signal ${String(t)}).`))},o=!1,handleOutput=e=>{if(o)return;let r=LOCAL_SERVER_URL_PATTERN.exec(e.toString(`utf8`));r!==null&&(o=!0,cleanup(),n({origin:normalizeOrigin(r[0]),process:i}))};i.once(`error`,handleError),i.once(`exit`,handleEarlyExit),i.stdout.on(`data`,e=>{process.stdout.write(e),handleOutput(e)}),i.stderr.on(`data`,e=>{process.stderr.write(e),handleOutput(e)})})}function installProcessShutdown(e){let t=e.process;if(t===void 0)return e;let close=()=>{process.off(`beforeExit`,close),process.off(`exit`,close),t.killed||t.kill()};return process.once(`beforeExit`,close),process.once(`exit`,close),e}function startAshDevServer(e){return startServerProcess({args:[createAshBinaryPath(),`dev`,`--no-repl`,`--port`,`0`],command:process.execPath,cwd:e}).then(e=>(process.env[ASH_BASE_URL_ENV]=e.origin,installProcessShutdown(e)))}async function resolveSharedAshDevServer(e){let t=await readUsableAshDevServerRegistry(e);if(t!==void 0)return process.env[ASH_BASE_URL_ENV]=t,{origin:t};let n=await acquireAshDevServerLock(e);try{let t=await readUsableAshDevServerRegistry(e);if(t!==void 0)return process.env[ASH_BASE_URL_ENV]=t,{origin:t};let n=await startAshDevServer(e);return await writeAshDevServerRegistry(e,n),n}finally{await n()}}export{ASH_BASE_URL_ENV,normalizeDevServerRegistry,resolveSharedAshDevServer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default, ASH_NUXT_SERVICE_PREFIX, type AshNuxtModuleOptions } from "./module.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ASH_NUXT_SERVICE_PREFIX}from"./routing.js";import module_default from"./module.js";export{ASH_NUXT_SERVICE_PREFIX,module_default as default};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ASH_NUXT_SERVICE_PREFIX } from "./routing.js";
|
|
2
|
+
export { ASH_NUXT_SERVICE_PREFIX };
|
|
3
|
+
/**
|
|
4
|
+
* Options for the Ash Nuxt module.
|
|
5
|
+
*/
|
|
6
|
+
export interface AshNuxtModuleOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Path to the Ash application root, relative to the Nuxt project root
|
|
9
|
+
* unless absolute. Defaults to the Nuxt project root.
|
|
10
|
+
*/
|
|
11
|
+
ashRoot?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Build command for the generated Ash Vercel service.
|
|
14
|
+
* Defaults to `"ash build"`.
|
|
15
|
+
*/
|
|
16
|
+
ashBuildCommand?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Set to `false` to skip creating or updating `vercel.json`.
|
|
19
|
+
*
|
|
20
|
+
* By default the module ensures `vercel.json` contains
|
|
21
|
+
* `experimentalServices` for the Nuxt app and Ash app.
|
|
22
|
+
*/
|
|
23
|
+
configureVercelJson?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Private Vercel service prefix for the Ash deployment. Must match the
|
|
26
|
+
* Ash service's `routePrefix` in `vercel.json`.
|
|
27
|
+
*/
|
|
28
|
+
servicePrefix?: string;
|
|
29
|
+
}
|
|
30
|
+
declare const _default: import("nuxt/schema").NuxtModule<AshNuxtModuleOptions, AshNuxtModuleOptions, false>;
|
|
31
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ASH_NUXT_SERVICE_PREFIX,createAshVercelRewriteRoute,joinRoutePrefix,normalizeOrigin,normalizeRoutePrefix,resolveProductionTarget}from"./routing.js";import{ASH_BASE_URL_ENV,resolveSharedAshDevServer}from"./dev-server.js";import{ensureAshVercelJson}from"./vercel-json.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{isAbsolute,resolve}from"node:path";import{addImports,defineNuxtModule,extendRouteRules}from"@nuxt/kit";function resolveApplicationRoot(e,t){return t===void 0||t.length===0?e:isAbsolute(t)?t:resolve(e,t)}async function resolveAshProxyTarget(e){if(!e.dev)return resolveProductionTarget(e.servicePrefix);let t=process.env[ASH_BASE_URL_ENV]?.trim();if(t&&t.length>0)return joinRoutePrefix(normalizeOrigin(t),ASH_ROUTE_PREFIX);let i=await resolveSharedAshDevServer(e.appRoot);return i.process!==void 0&&e.onDevServerSpawned?.(i.process),joinRoutePrefix(i.origin,ASH_ROUTE_PREFIX)}var module_default=defineNuxtModule({meta:{name:`experimental-ash`,configKey:`ash`,compatibility:{nuxt:`>=4.0.0`}},defaults:{},async setup(e,n){let r=n.options.rootDir,a=resolveApplicationRoot(r,e.ashRoot),o=normalizeRoutePrefix(e.servicePrefix??`/_ash_internal/ash`),s=e.configureVercelJson!==!1;if(addImports({name:`useAshAgent`,from:`experimental-ash/vue`}),!n.options.dev&&process.env.VERCEL){let e=createAshVercelRewriteRoute(o),r=n.options.nitro,i=r.vercel?.config;r.vercel={...r.vercel,config:{version:3,...i,routes:[e,...i?.routes??[]]}}}else n.hook(`modules:done`,async()=>{let e=await resolveAshProxyTarget({appRoot:a,dev:n.options.dev,servicePrefix:o,onDevServerSpawned:e=>{n.hook(`close`,()=>{e.killed||e.kill()})}});extendRouteRules(`${ASH_ROUTE_PREFIX}/**`,{proxy:`${e}/**`})});s&&await ensureAshVercelJson({appRoot:a,ashBuildCommand:e.ashBuildCommand??`ash build`,nuxtRoot:r,servicePrefix:o})}});export{ASH_NUXT_SERVICE_PREFIX,module_default as default};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private route namespace used when a Vercel deployment hosts Ash as a
|
|
3
|
+
* separate experimental service behind the Nuxt app.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ASH_NUXT_SERVICE_PREFIX = "/_ash_internal/ash";
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a user-supplied service prefix into a leading-slash, no-trailing-
|
|
8
|
+
* slash route. Throws when the prefix resolves to the root route, which would
|
|
9
|
+
* collide with the Nuxt web service.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeRoutePrefix(prefix: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Join a route prefix and a path with exactly one separating slash.
|
|
14
|
+
*/
|
|
15
|
+
export declare function joinRoutePrefix(prefix: string, path: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Reduce an origin string to its canonical `protocol://host[:port]` form.
|
|
18
|
+
*/
|
|
19
|
+
export declare function normalizeOrigin(origin: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the local production port the module proxies to when an Ash service
|
|
22
|
+
* runs alongside a non-Vercel Nuxt deployment. Defaults to
|
|
23
|
+
* {@link DEFAULT_ASH_NUXT_PRODUCTION_PORT}.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readLocalProductionPort(): number;
|
|
26
|
+
/**
|
|
27
|
+
* An edge-level Vercel rewrite expressed in Build Output API v3 form.
|
|
28
|
+
*/
|
|
29
|
+
export interface AshVercelRewriteRoute {
|
|
30
|
+
readonly src: string;
|
|
31
|
+
readonly dest: string;
|
|
32
|
+
/**
|
|
33
|
+
* Re-run route matching against the rewritten `dest`. Required so the
|
|
34
|
+
* rewritten Ash service path is routed to the sibling Ash service instead of
|
|
35
|
+
* being resolved inside the host service's own filesystem (which 404s).
|
|
36
|
+
*/
|
|
37
|
+
readonly check: true;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build the edge-level Vercel rewrite that forwards Ash transport requests
|
|
41
|
+
* (`/ash/v1/**`) to the Ash service prefix (`/_ash_internal/ash/ash/v1/**`).
|
|
42
|
+
*
|
|
43
|
+
* Mirrors the Next.js integration's `beforeFiles` rewrite. A Nitro runtime
|
|
44
|
+
* `proxy` route rule cannot reach a sibling Vercel service — the proxied
|
|
45
|
+
* request loops back into the Nuxt function and 404s — so production routing
|
|
46
|
+
* must happen at the edge via the build output config instead.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createAshVercelRewriteRoute(servicePrefix: string): AshVercelRewriteRoute;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the proxy destination for Ash routes in production.
|
|
51
|
+
*
|
|
52
|
+
* On Vercel the destination is the private service prefix. Off Vercel it is an
|
|
53
|
+
* explicit origin override (`ASH_NUXT_PRODUCTION_ORIGIN`) or a local port.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveProductionTarget(servicePrefix: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";const ASH_NUXT_SERVICE_PREFIX=`/_ash_internal/ash`,ASH_NUXT_PRODUCTION_PORT_ENV=`ASH_NUXT_PRODUCTION_PORT`;function normalizeRoutePrefix(e){let t=(e.startsWith(`/`)?e:`/${e}`).replace(/\/+$/,``);if(t.length===0)throw Error(`Ash Nuxt service prefix cannot resolve to the root route.`);return t}function joinRoutePrefix(e,t){return`${e.replace(/\/+$/,``)}/${t.replace(/^\/+/,``)}`}function normalizeOrigin(e){return new URL(e.trim()).origin}function readLocalProductionPort(){let e=process.env[ASH_NUXT_PRODUCTION_PORT_ENV];if(e===void 0||e.trim().length===0)return 4274;let t=Number.parseInt(e,10);if(String(t)!==e.trim()||t<1||t>65535)throw Error(`${ASH_NUXT_PRODUCTION_PORT_ENV} must be an integer between 1 and 65535.`);return t}function createAshVercelRewriteRoute(t){let n=joinRoutePrefix(t,ASH_ROUTE_PREFIX);return{src:`^${ASH_ROUTE_PREFIX}/(.*)$`,dest:`${n}/$1`,check:!0}}function resolveProductionTarget(t){if(process.env.VERCEL)return joinRoutePrefix(t,ASH_ROUTE_PREFIX);let n=process.env.ASH_NUXT_PRODUCTION_ORIGIN;return n!==void 0&&n.trim().length>0?joinRoutePrefix(normalizeOrigin(n),ASH_ROUTE_PREFIX):joinRoutePrefix(`http://127.0.0.1:${String(readLocalProductionPort())}`,ASH_ROUTE_PREFIX)}export{ASH_NUXT_SERVICE_PREFIX,createAshVercelRewriteRoute,joinRoutePrefix,normalizeOrigin,normalizeRoutePrefix,readLocalProductionPort,resolveProductionTarget};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface EnsureVercelJsonResult {
|
|
2
|
+
readonly servicePrefix: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Ensure `vercel.json` declares the Nuxt web service and the Ash agent
|
|
6
|
+
* service so a Vercel deployment ships both from one project.
|
|
7
|
+
*
|
|
8
|
+
* Existing services are preserved untouched; an already-configured Ash
|
|
9
|
+
* service's `routePrefix` wins over {@link input.servicePrefix}. The file is
|
|
10
|
+
* only rewritten when the resulting config differs from what is on disk.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ensureAshVercelJson(input: {
|
|
13
|
+
readonly appRoot: string;
|
|
14
|
+
readonly ashBuildCommand: string;
|
|
15
|
+
readonly nuxtRoot: string;
|
|
16
|
+
readonly servicePrefix: string;
|
|
17
|
+
}): Promise<EnsureVercelJsonResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{join,relative}from"node:path";import{readFile,writeFile}from"node:fs/promises";const VERCEL_JSON_FILE_NAME=`vercel.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveRelativeEntrypoint(e,n){let r=relative(e,n);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}function normalizeVercelJsonConfig(e){if(!isRecord(e))throw Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);let t=e.experimentalServices;if(t!==void 0&&!isRecord(t))throw Error(`${VERCEL_JSON_FILE_NAME} experimentalServices must be a JSON object.`);return e}async function readVercelJsonConfig(e){try{return normalizeVercelJsonConfig(JSON.parse(await readFile(e,`utf8`)))}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}async function ensureAshVercelJson(t){let n=join(t.
|
|
1
|
+
import{join,relative}from"node:path";import{readFile,writeFile}from"node:fs/promises";const VERCEL_JSON_FILE_NAME=`vercel.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveRelativeEntrypoint(e,n){let r=relative(e,n);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}function normalizeVercelJsonConfig(e){if(!isRecord(e))throw Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);let t=e.experimentalServices;if(t!==void 0&&!isRecord(t))throw Error(`${VERCEL_JSON_FILE_NAME} experimentalServices must be a JSON object.`);return e}async function readVercelJsonConfig(e){try{return normalizeVercelJsonConfig(JSON.parse(await readFile(e,`utf8`)))}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}async function ensureAshVercelJson(t){let n=join(t.nuxtRoot,VERCEL_JSON_FILE_NAME),i=await readVercelJsonConfig(n),a=resolveRelativeEntrypoint(t.nuxtRoot,t.appRoot),o=i.experimentalServices??{},s=findServiceByFramework(o,`ash`),c=findServiceByFramework(o,`nuxtjs`),l=s?.routePrefix??t.servicePrefix,u={...o};c===void 0&&(u.web={entrypoint:`.`,framework:`nuxtjs`,routePrefix:`/`}),s===void 0&&(u.ash={buildCommand:t.ashBuildCommand,entrypoint:a,framework:`ash`,routePrefix:l});let d={...i,$schema:i.$schema??`https://openapi.vercel.sh/vercel.json`,experimentalServices:u};return JSON.stringify(i)!==JSON.stringify(d)&&await writeFile(n,`${JSON.stringify(d,null,2)}\n`),{servicePrefix:l}}export{ensureAshVercelJson};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
export declare const ASH_BASE_URL_ENV = "ASH_BASE_URL";
|
|
3
|
+
export interface AshProcessHandle {
|
|
4
|
+
readonly origin: string;
|
|
5
|
+
readonly process?: ChildProcess;
|
|
6
|
+
}
|
|
7
|
+
export interface AshDevServerRegistry {
|
|
8
|
+
readonly appRoot: string;
|
|
9
|
+
readonly origin: string;
|
|
10
|
+
readonly pid: number | null;
|
|
11
|
+
readonly updatedAt: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse and validate a persisted dev-server registry record. Returns
|
|
15
|
+
* `undefined` for anything that is not a well-formed registry so callers fall
|
|
16
|
+
* back to spawning a fresh server.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeDevServerRegistry(value: unknown): AshDevServerRegistry | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a shared Ash dev server for {@link appRoot}, reusing a healthy
|
|
21
|
+
* registered server when one exists and otherwise spawning a new one behind a
|
|
22
|
+
* cross-process lock so concurrent SvelteKit processes don't each boot Ash.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveSharedAshDevServer(appRoot: string): Promise<AshProcessHandle>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{joinRoutePrefix,normalizeOrigin}from"./routing.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{join}from"node:path";import{mkdir,open,readFile,rm,stat,writeFile}from"node:fs/promises";import{spawn}from"node:child_process";import{resolvePackageRoot}from"#internal/application/package.js";const ASH_BASE_URL_ENV=`ASH_BASE_URL`,DEFAULT_SERVER_READY_TIMEOUT_MS=3e4,DEV_SERVER_REGISTRY_TIMEOUT_MS=3e4,LOCAL_SERVER_URL_PATTERN=/https?:\/\/(?:\[[^\]\s]+\]|[^\s/:[\]]+)(?::\d+)?/;function isNodeErrorWithCode(e,t){return e instanceof Error&&`code`in e&&e.code===t}function delay(e){return new Promise(t=>setTimeout(t,e))}function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveAshCacheDirectory(e){return join(e,`.ash`)}function resolveAshDevServerRegistryPath(e){return join(resolveAshCacheDirectory(e),`sveltekit-dev-server.json`)}function resolveAshDevServerLockPath(e){return join(resolveAshCacheDirectory(e),`sveltekit-dev-server.lock`)}function normalizeDevServerRegistry(e){if(isRecord(e)&&!(typeof e.appRoot!=`string`||typeof e.origin!=`string`||typeof e.updatedAt!=`string`)&&!(e.pid!==null&&typeof e.pid!=`number`))try{return{appRoot:e.appRoot,origin:normalizeOrigin(e.origin),pid:e.pid,updatedAt:e.updatedAt}}catch{return}}async function isAshServerHealthy(t){let r=new AbortController,i=setTimeout(()=>r.abort(),1e3);try{return(await fetch(joinRoutePrefix(t,`${ASH_ROUTE_PREFIX}/health`),{signal:r.signal})).ok}catch{return!1}finally{clearTimeout(i)}}async function readUsableAshDevServerRegistry(e){try{let t=normalizeDevServerRegistry(JSON.parse(await readFile(resolveAshDevServerRegistryPath(e),`utf8`)));return t===void 0||t.appRoot!==e||!await isAshServerHealthy(t.origin)?void 0:t.origin}catch(e){if(isNodeErrorWithCode(e,`ENOENT`))return;throw e}}async function writeAshDevServerRegistry(e,t){await mkdir(resolveAshCacheDirectory(e),{recursive:!0}),await writeFile(resolveAshDevServerRegistryPath(e),`${JSON.stringify({appRoot:e,origin:t.origin,pid:t.process?.pid??null,updatedAt:new Date().toISOString()},null,2)}\n`)}async function removeStaleAshDevServerLock(e){try{let t=await stat(e);Date.now()-t.mtimeMs>3e4&&await rm(e,{force:!0})}catch(e){if(!isNodeErrorWithCode(e,`ENOENT`))throw e}}async function acquireAshDevServerLock(e){let t=resolveAshCacheDirectory(e),n=resolveAshDevServerLockPath(e),r=Date.now()+DEV_SERVER_REGISTRY_TIMEOUT_MS;for(await mkdir(t,{recursive:!0});;)try{let e=await open(n,`wx`);return await e.writeFile(`${String(process.pid)}\n`),await e.close(),async()=>{await rm(n,{force:!0})}}catch(t){if(!isNodeErrorWithCode(t,`EEXIST`))throw t;if(await readUsableAshDevServerRegistry(e)!==void 0)return async()=>{};if(await removeStaleAshDevServerLock(n),Date.now()>r)throw Error(`Timed out after ${DEV_SERVER_REGISTRY_TIMEOUT_MS}ms waiting for another SvelteKit process to start Ash.`);await delay(100)}}function createAshBinaryPath(){return join(resolvePackageRoot(),`bin`,`ash.js`)}function startServerProcess(e){return new Promise((n,r)=>{let i=spawn(e.command,e.args,{cwd:e.cwd,env:{...process.env,...e.env},stdio:[`ignore`,`pipe`,`pipe`]}),a=setTimeout(()=>{i.kill(),r(Error(`Timed out after ${DEFAULT_SERVER_READY_TIMEOUT_MS}ms waiting for Ash to print its server URL.`))},DEFAULT_SERVER_READY_TIMEOUT_MS),cleanup=()=>{clearTimeout(a),i.off(`error`,handleError),i.off(`exit`,handleEarlyExit)},handleError=e=>{cleanup(),r(e)},handleEarlyExit=(e,t)=>{cleanup(),r(Error(`Ash server process exited before printing its server URL (code ${String(e)}, signal ${String(t)}).`))},o=!1,handleOutput=e=>{if(o)return;let r=LOCAL_SERVER_URL_PATTERN.exec(e.toString(`utf8`));r!==null&&(o=!0,cleanup(),n({origin:normalizeOrigin(r[0]),process:i}))};i.once(`error`,handleError),i.once(`exit`,handleEarlyExit),i.stdout.on(`data`,e=>{process.stdout.write(e),handleOutput(e)}),i.stderr.on(`data`,e=>{process.stderr.write(e),handleOutput(e)})})}function installProcessShutdown(e){let t=e.process;if(t===void 0)return e;let close=()=>{process.off(`beforeExit`,close),process.off(`exit`,close),t.killed||t.kill()};return process.once(`beforeExit`,close),process.once(`exit`,close),e}function startAshDevServer(e){return startServerProcess({args:[createAshBinaryPath(),`dev`,`--no-repl`,`--port`,`0`],command:process.execPath,cwd:e}).then(e=>(process.env[ASH_BASE_URL_ENV]=e.origin,installProcessShutdown(e)))}async function resolveSharedAshDevServer(e){let t=await readUsableAshDevServerRegistry(e);if(t!==void 0)return process.env[ASH_BASE_URL_ENV]=t,{origin:t};let n=await acquireAshDevServerLock(e);try{let t=await readUsableAshDevServerRegistry(e);if(t!==void 0)return process.env[ASH_BASE_URL_ENV]=t,{origin:t};let n=await startAshDevServer(e);return await writeAshDevServerRegistry(e,n),n}finally{await n()}}export{ASH_BASE_URL_ENV,normalizeDevServerRegistry,resolveSharedAshDevServer};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { ASH_SVELTEKIT_SERVICE_PREFIX } from "./routing.js";
|
|
3
|
+
export { ASH_SVELTEKIT_SERVICE_PREFIX };
|
|
4
|
+
/**
|
|
5
|
+
* Options for the Ash SvelteKit Vite plugin.
|
|
6
|
+
*/
|
|
7
|
+
export interface AshSvelteKitPluginOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Path to the Ash application root, relative to the SvelteKit project root
|
|
10
|
+
* unless absolute. Defaults to the SvelteKit project root.
|
|
11
|
+
*/
|
|
12
|
+
readonly ashRoot?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Build command for the generated Ash Vercel service.
|
|
15
|
+
* Defaults to `"ash build"`.
|
|
16
|
+
*/
|
|
17
|
+
readonly ashBuildCommand?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Set to `false` to skip creating or updating `vercel.json`.
|
|
20
|
+
*
|
|
21
|
+
* By default the plugin ensures `vercel.json` contains `experimentalServices`
|
|
22
|
+
* for the SvelteKit app and Ash app.
|
|
23
|
+
*/
|
|
24
|
+
readonly configureVercelJson?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Private Vercel service prefix for the Ash deployment. Must match the
|
|
27
|
+
* Ash service's `routePrefix` in `vercel.json`.
|
|
28
|
+
*/
|
|
29
|
+
readonly servicePrefix?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Vite plugin for running an Ash agent alongside a SvelteKit app.
|
|
33
|
+
*
|
|
34
|
+
* In development and local preview, `ashSvelteKit` starts
|
|
35
|
+
* `ash dev --no-repl --port 0` for the Ash app and proxies Ash protocol
|
|
36
|
+
* endpoints to that local URL. During builds it can ensure `vercel.json`
|
|
37
|
+
* deploys the SvelteKit app and Ash agent as sibling Vercel services.
|
|
38
|
+
*/
|
|
39
|
+
export declare function ashSvelteKit(options?: AshSvelteKitPluginOptions): Plugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ASH_SVELTEKIT_SERVICE_PREFIX,normalizeOrigin,normalizeRoutePrefix}from"./routing.js";import{ASH_BASE_URL_ENV,resolveSharedAshDevServer}from"./dev-server.js";import{ensureAshVercelJson}from"./vercel-json.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{isAbsolute,resolve}from"node:path";function resolveApplicationRoot(e,t){return t===void 0||t.length===0?e:isAbsolute(t)?t:resolve(e,t)}function mergeProxyConfig(e,t){return{...e,[ASH_ROUTE_PREFIX]:{changeOrigin:!0,target:t}}}async function resolveAshDevProxyTarget(e){let n=process.env[ASH_BASE_URL_ENV]?.trim();return n&&n.length>0?normalizeOrigin(n):(await resolveSharedAshDevServer(e)).origin}function ashSvelteKit(e={}){let t=process.cwd(),r=resolveApplicationRoot(t,e.ashRoot),i=normalizeRoutePrefix(e.servicePrefix??`/_ash_internal/ash`),a=e.configureVercelJson!==!1;return{name:`experimental-ash:sveltekit`,async config(n,o){if(t=n.root===void 0?process.cwd():resolve(process.cwd(),n.root),r=resolveApplicationRoot(t,e.ashRoot),a&&o.command===`build`&&await ensureAshVercelJson({appRoot:r,ashBuildCommand:e.ashBuildCommand??`ash build`,servicePrefix:i,svelteKitRoot:t}),o.command!==`serve`)return{};let s=await resolveAshDevProxyTarget(r);return o.isPreview?{preview:{proxy:mergeProxyConfig(n.preview?.proxy,s)}}:{server:{proxy:mergeProxyConfig(n.server?.proxy,s)}}}}}export{ASH_SVELTEKIT_SERVICE_PREFIX,ashSvelteKit};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private route namespace used when a Vercel deployment hosts Ash as a
|
|
3
|
+
* separate experimental service behind the SvelteKit app.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ASH_SVELTEKIT_SERVICE_PREFIX = "/_ash_internal/ash";
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a user-supplied service prefix into a leading-slash, no-trailing-
|
|
8
|
+
* slash route. Throws when the prefix resolves to the root route, which would
|
|
9
|
+
* collide with the SvelteKit web service.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeRoutePrefix(prefix: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Join a route prefix and a path with exactly one separating slash.
|
|
14
|
+
*/
|
|
15
|
+
export declare function joinRoutePrefix(prefix: string, path: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Reduce an origin string to its canonical `protocol://host[:port]` form.
|
|
18
|
+
*/
|
|
19
|
+
export declare function normalizeOrigin(origin: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* A Vercel rewrite that forwards Ash transport requests (`/ash/v1/**`) to the
|
|
22
|
+
* private Ash service prefix (`/_ash_internal/ash/ash/v1/**`).
|
|
23
|
+
*/
|
|
24
|
+
export interface AshVercelRewrite {
|
|
25
|
+
readonly destination: string;
|
|
26
|
+
readonly source: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the Vercel rewrite that forwards browser Ash transport requests to the
|
|
30
|
+
* sibling Ash service.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createAshVercelRewrite(servicePrefix: string): AshVercelRewrite;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";const ASH_SVELTEKIT_SERVICE_PREFIX=`/_ash_internal/ash`;function normalizeRoutePrefix(e){let t=(e.startsWith(`/`)?e:`/${e}`).replace(/\/+$/,``);if(t.length===0)throw Error(`Ash SvelteKit service prefix cannot resolve to the root route.`);return t}function joinRoutePrefix(e,t){return`${e.replace(/\/+$/,``)}/${t.replace(/^\/+/,``)}`}function normalizeOrigin(e){return new URL(e.trim()).origin}function createAshVercelRewrite(t){return{destination:`${joinRoutePrefix(t,ASH_ROUTE_PREFIX)}/:path*`,source:`${ASH_ROUTE_PREFIX}/:path*`}}export{ASH_SVELTEKIT_SERVICE_PREFIX,createAshVercelRewrite,joinRoutePrefix,normalizeOrigin,normalizeRoutePrefix};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface EnsureVercelJsonResult {
|
|
2
|
+
readonly servicePrefix: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Ensure `vercel.json` declares the SvelteKit web service and the Ash agent
|
|
6
|
+
* service so a Vercel deployment ships both from one project.
|
|
7
|
+
*
|
|
8
|
+
* Existing services are preserved untouched; an already-configured Ash
|
|
9
|
+
* service's `routePrefix` wins over {@link input.servicePrefix}. The file is
|
|
10
|
+
* only rewritten when the resulting config differs from what is on disk.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ensureAshVercelJson(input: {
|
|
13
|
+
readonly appRoot: string;
|
|
14
|
+
readonly ashBuildCommand: string;
|
|
15
|
+
readonly servicePrefix: string;
|
|
16
|
+
readonly svelteKitRoot: string;
|
|
17
|
+
}): Promise<EnsureVercelJsonResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createAshVercelRewrite}from"./routing.js";import{join,relative}from"node:path";import{readFile,writeFile}from"node:fs/promises";const VERCEL_JSON_FILE_NAME=`vercel.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveRelativeEntrypoint(e,t){let r=relative(e,t);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}function normalizeVercelJsonConfig(e){if(!isRecord(e))throw Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);let t=e.experimentalServices;if(t!==void 0&&!isRecord(t))throw Error(`${VERCEL_JSON_FILE_NAME} experimentalServices must be a JSON object.`);let n=e.rewrites;if(n!==void 0&&!Array.isArray(n))throw Error(`${VERCEL_JSON_FILE_NAME} rewrites must be an array.`);return e}async function readVercelJsonConfig(e){try{return normalizeVercelJsonConfig(JSON.parse(await readFile(e,`utf8`)))}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}function hasRewrite(e,t){return e.some(e=>e.source===t.source&&e.destination===t.destination)}async function ensureAshVercelJson(n){let r=join(n.svelteKitRoot,VERCEL_JSON_FILE_NAME),i=await readVercelJsonConfig(r),a=resolveRelativeEntrypoint(n.svelteKitRoot,n.appRoot),o=i.experimentalServices??{},s=findServiceByFramework(o,`ash`),c=findServiceByFramework(o,`sveltekit`),l=s?.routePrefix??n.servicePrefix,u={...o};c===void 0&&(u.web={entrypoint:`.`,framework:`sveltekit`,routePrefix:`/`}),s===void 0&&(u.ash={buildCommand:n.ashBuildCommand,entrypoint:a,framework:`ash`,routePrefix:l});let d=createAshVercelRewrite(l),f=i.rewrites??[],p={...i,$schema:i.$schema??`https://openapi.vercel.sh/vercel.json`,experimentalServices:u,rewrites:hasRewrite(f,d)?f:[d,...f]};return JSON.stringify(i)!==JSON.stringify(p)&&await writeFile(r,`${JSON.stringify(p,null,2)}\n`),{servicePrefix:l}}export{ensureAshVercelJson};
|
|
@@ -1,36 +1,15 @@
|
|
|
1
|
+
import { type AshAgentStoreCallbacks, type AshAgentStoreSnapshot, type AshAgentStoreStatus, type PrepareSend } from "#client/ash-agent-store.js";
|
|
1
2
|
import type { AshAgentReducer } from "#client/reducer.js";
|
|
2
3
|
import type { ClientSession } from "#client/session.js";
|
|
3
4
|
import { type AshMessageData } from "#client/message-reducer.js";
|
|
4
5
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
5
6
|
import type { ClientAuth, HeadersValue, SendMessageOptions, SendTurnInput, SessionState } from "#client/types.js";
|
|
6
|
-
export type
|
|
7
|
-
|
|
8
|
-
* Prepares one outbound turn immediately before the client sends it.
|
|
9
|
-
*
|
|
10
|
-
* Use this to attach fresh, one-turn client state such as page context via
|
|
11
|
-
* `clientContext`.
|
|
12
|
-
*/
|
|
13
|
-
export type PrepareSend = (input: SendTurnInput) => SendTurnInput | Promise<SendTurnInput>;
|
|
7
|
+
export type { PrepareSend };
|
|
8
|
+
export type UseAshAgentStatus = AshAgentStoreStatus;
|
|
14
9
|
/**
|
|
15
10
|
* Current projected state for an Ash agent session.
|
|
16
11
|
*/
|
|
17
|
-
export
|
|
18
|
-
readonly data: TData;
|
|
19
|
-
readonly error: Error | undefined;
|
|
20
|
-
readonly events: readonly HandleMessageStreamEvent[];
|
|
21
|
-
readonly session: SessionState;
|
|
22
|
-
readonly status: UseAshAgentStatus;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Lifecycle callbacks invoked while `useAshAgent` processes a turn.
|
|
26
|
-
*/
|
|
27
|
-
interface UseAshAgentCallbacks<TData> {
|
|
28
|
-
readonly onError?: (error: Error) => void;
|
|
29
|
-
readonly onEvent?: (event: HandleMessageStreamEvent) => void;
|
|
30
|
-
readonly onFinish?: (snapshot: UseAshAgentSnapshot<TData>) => void;
|
|
31
|
-
readonly onSessionChange?: (session: SessionState) => void;
|
|
32
|
-
readonly prepareSend?: PrepareSend;
|
|
33
|
-
}
|
|
12
|
+
export type UseAshAgentSnapshot<TData> = AshAgentStoreSnapshot<TData>;
|
|
34
13
|
/**
|
|
35
14
|
* Snapshot plus commands returned by `useAshAgent`.
|
|
36
15
|
*/
|
|
@@ -52,7 +31,7 @@ export interface UseAshAgentHelpers<TData> extends UseAshAgentSnapshot<TData> {
|
|
|
52
31
|
* values to `auth` or `headers`; the underlying client resolves those before
|
|
53
32
|
* each HTTP request.
|
|
54
33
|
*/
|
|
55
|
-
export interface UseAshAgentOptions<TData> extends
|
|
34
|
+
export interface UseAshAgentOptions<TData> extends AshAgentStoreCallbacks<TData> {
|
|
56
35
|
readonly auth?: ClientAuth;
|
|
57
36
|
readonly headers?: HeadersValue;
|
|
58
37
|
/**
|
|
@@ -85,4 +64,3 @@ export declare function useAshAgent(options?: UseAshAgentOptions<AshMessageData>
|
|
|
85
64
|
export declare function useAshAgent<TData>(options: UseAshAgentOptions<TData> & {
|
|
86
65
|
readonly reducer: AshAgentReducer<TData>;
|
|
87
66
|
}): UseAshAgentHelpers<TData>;
|
|
88
|
-
export {};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`)}function isAbortError(e){return e instanceof Error&&e.name===`AbortError`}function toTerminalStreamFailureError(e){if(e.type!==`session.failed`)return;let t=Error(e.data.message);return t.name=e.data.code,t}export{useAshAgent};
|
|
1
|
+
import{AshAgentStore}from"#client/ash-agent-store.js";import{defaultMessageReducer}from"#client/message-reducer.js";import{useCallback,useMemo,useRef,useSyncExternalStore}from"react";function useAshAgent(r={}){let i=useRef(void 0);if(!i.current){let n=r.reducer??defaultMessageReducer();i.current=new AshAgentStore({auth:r.auth,headers:r.headers,host:r.host,initialEvents:r.initialEvents,initialSession:r.initialSession,maxReconnectAttempts:r.maxReconnectAttempts,optimistic:r.optimistic,reducer:n,session:r.session})}let a=i.current;a.setCallbacks({onError:r.onError,onEvent:r.onEvent,onFinish:r.onFinish,onSessionChange:r.onSessionChange,prepareSend:r.prepareSend});let o=useSyncExternalStore(useCallback(e=>a.subscribe(e),[a]),()=>a.snapshot,()=>a.snapshot),s=useCallback(()=>a.reset(),[a]),c=useCallback((e,t)=>a.send(e,t),[a]),l=useCallback((e,t)=>a.sendMessage(e,t),[a]),u=useCallback(()=>a.stop(),[a]);return useMemo(()=>({...o,reset:s,send:c,sendMessage:l,stop:u}),[s,c,l,o,u])}export{useAshAgent};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ToolSet } from "ai";
|
|
2
|
+
import type { ResolvedConnectionDefinition } from "#runtime/types.js";
|
|
3
|
+
import { type OpenApiOperation } from "#runtime/connections/openapi-operations.js";
|
|
4
|
+
import type { ConnectionClient, ConnectionToolMetadata } from "#runtime/connections/types.js";
|
|
5
|
+
interface OpenApiToolCache {
|
|
6
|
+
readonly metadata: readonly ConnectionToolMetadata[];
|
|
7
|
+
readonly operations: ReadonlyMap<string, OpenApiOperation>;
|
|
8
|
+
readonly tools: ToolSet;
|
|
9
|
+
readonly baseUrl: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Result of executing an OpenAPI operation. Returned to the model as the
|
|
13
|
+
* tool result so it can react to the status and body of any response,
|
|
14
|
+
* including non-2xx responses.
|
|
15
|
+
*/
|
|
16
|
+
export interface OpenApiToolResult {
|
|
17
|
+
readonly status: number;
|
|
18
|
+
readonly statusText: string;
|
|
19
|
+
readonly body: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A {@link ConnectionClient} that turns an OpenAPI 3.x document into
|
|
23
|
+
* connection tools.
|
|
24
|
+
*
|
|
25
|
+
* Created lazily per-connection per-session. On first use it loads the
|
|
26
|
+
* document (fetching it when `spec` is a URL), dereferences local
|
|
27
|
+
* `$ref` pointers, and maps each operation to a tool whose name is the
|
|
28
|
+
* operation's `operationId`. Tool calls reconstruct the HTTP request —
|
|
29
|
+
* substituting path parameters, appending query parameters, attaching
|
|
30
|
+
* resolved auth and headers — and return the response as a serializable
|
|
31
|
+
* `{ status, statusText, body }`.
|
|
32
|
+
*/
|
|
33
|
+
export declare class OpenApiConnectionClient implements ConnectionClient {
|
|
34
|
+
#private;
|
|
35
|
+
constructor(connection: ResolvedConnectionDefinition);
|
|
36
|
+
/** Loads and parses the OpenAPI document, sharing one in-flight load. */
|
|
37
|
+
connect(): Promise<OpenApiToolCache>;
|
|
38
|
+
getToolMetadata(): Promise<readonly ConnectionToolMetadata[]>;
|
|
39
|
+
getTools(): Promise<ToolSet>;
|
|
40
|
+
executeTool(toolName: string, args: unknown): Promise<OpenApiToolResult>;
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isObject}from"#shared/guards.js";import{jsonSchema,tool}from"ai";import{passesToolFilter,resolveHeaders}from"#runtime/connections/mcp-client.js";import{HTTP_METHODS,operationDescription,operationName,uniqueName}from"#runtime/connections/openapi-operations.js";import{buildInputSchema,deref,derefSchema,isArray}from"#runtime/connections/openapi-schema.js";import{applySecurity,resolveSecurity}from"#runtime/connections/openapi-security.js";import{extractServerUrl,parseSpecDocument}from"#runtime/connections/openapi-spec.js";var OpenApiConnectionClient=class{#e;#t;#n;constructor(e){this.#n=e}async connect(){return this.#r()}async getToolMetadata(){return(await this.#r()).metadata}async getTools(){return(await this.#r()).tools}async executeTool(t,n){let r=await this.#r(),i=r.operations.get(t);if(i===void 0)throw Error(`Tool "${t}" not found in connection "${this.#n.connectionName}".`);return this.#u(i,r.baseUrl,isObject(n)?n:{})}async close(){this.#e=void 0,this.#t=void 0}async#r(){if(this.#t!==void 0)return this.#t;if(this.#e!==void 0)return this.#e;this.#e=this.#i();try{return this.#t=await this.#e,this.#t}catch(e){throw this.#e=void 0,e}}async#i(){let i=await this.#o(),a=this.#a(i),o=this.#s(i),s=this.#n.tools,c=s===void 0?o:o.filter(e=>passesToolFilter(e.toolName,s)),l=[],u=new Map,d={};for(let r of c)u.set(r.toolName,r),l.push({description:r.description,inputSchema:r.inputSchema,name:r.toolName}),d[r.toolName]=tool({description:r.description,inputSchema:jsonSchema(r.inputSchema),execute:async t=>this.#u(r,a,isObject(t)?t:{})});return{metadata:l,operations:u,tools:d,baseUrl:a}}#a(e){let t=this.#n.url;if(typeof t==`string`&&t.trim().length>0)return t;let n=extractServerUrl(e,this.#n.spec);if(n!==void 0)return n;throw Error(`OpenAPI connection "${this.#n.connectionName}" has no base URL: set "baseUrl" or ensure the document declares an absolute "servers" entry.`)}async#o(){let t=this.#n.spec;if(t===void 0)throw Error(`OpenAPI connection "${this.#n.connectionName}" is missing its "spec" source.`);if(typeof t!=`string`)return t;let n;try{n=await fetch(t,{headers:{accept:`application/json, application/yaml, text/yaml, */*`}})}catch(e){throw Error(`OpenAPI connection "${this.#n.connectionName}" failed to fetch its spec from "${t}": ${String(e)}`)}if(!n.ok)throw Error(`OpenAPI connection "${this.#n.connectionName}" failed to fetch its spec from "${t}": HTTP ${n.status}.`);let r=await n.text(),i;try{i=parseSpecDocument(r)}catch(e){throw Error(`OpenAPI connection "${this.#n.connectionName}" spec at "${t}" could not be parsed as JSON or YAML: ${String(e)}`)}if(!isObject(i))throw Error(`OpenAPI connection "${this.#n.connectionName}" spec at "${t}" is not an OpenAPI document object.`);return i}#s(t){let n=t.paths;if(!isObject(n))return[];let r=[],i=new Set;for(let[u,d]of Object.entries(n)){if(!isObject(d))continue;let n=isArray(d.parameters)?d.parameters:[];for(let f of HTTP_METHODS){let a=d[f];if(!isObject(a))continue;let p=uniqueName(operationName(a,f,u),i),m=isArray(a.parameters)?a.parameters:[],h=this.#c(t,[...n,...m]),g=this.#l(t,a.requestBody);r.push({toolName:p,method:f,pathTemplate:u,description:operationDescription(a),parameters:h,requestBody:g,inputSchema:buildInputSchema(h,g),security:resolveSecurity(t,a)})}}return r}#c(t,n){let r=[];for(let i of n){let n=isObject(i)?deref(t,i):i;if(!isObject(n))continue;let a=n.in;if(a!==`path`&&a!==`query`&&a!==`header`&&a!==`cookie`||typeof n.name!=`string`)continue;let o=isObject(n.schema)?derefSchema(t,n.schema):{};r.push({name:n.name,location:a,required:n.required===!0||a===`path`,schema:o,description:typeof n.description==`string`?n.description:void 0})}return r}#l(t,n){if(!isObject(n))return;let r=deref(t,n);if(!isObject(r)||!isObject(r.content))return;let i=r.content,a=`application/json`in i?`application/json`:Object.keys(i)[0];if(a===void 0)return;let o=i[a],s=isObject(o)&&isObject(o.schema)?derefSchema(t,o.schema):{};return{required:r.required===!0,contentType:a,schema:s}}async#u(e,t,n){let r=await resolveHeaders(this.#n),a=e.pathTemplate,o=new URLSearchParams,s=[];for(let t of e.parameters){let e=n[t.name];e!=null&&(t.location===`path`?a=a.replace(`{${t.name}}`,encodeURIComponent(String(e))):t.location===`query`?appendQuery(o,t.name,e):t.location===`cookie`?s.push(`${t.name}=${encodeURIComponent(String(e))}`):r[t.name]=String(e))}if(applySecurity(e.security,this.#n,r,o,s),s.length>0){let e=r.cookie??r.Cookie;delete r.Cookie,r.cookie=[e,...s].filter(e=>!!e).join(`; `)}let c=new URL(joinPath(t,a));c.search=o.toString();let l;e.requestBody!==void 0&&n.body!==void 0&&(l=JSON.stringify(n.body),r[`content-type`]=e.requestBody.contentType);let u=await fetch(c,{method:e.method.toUpperCase(),headers:r,body:l});return{status:u.status,statusText:u.statusText,body:await readResponseBody(u)}}};function appendQuery(e,t,n){if(isArray(n)){for(let r of n)e.append(t,String(r));return}e.append(t,String(n))}function joinPath(e,t){return`${e.endsWith(`/`)?e.slice(0,-1):e}${t.startsWith(`/`)?t:`/${t}`}`}async function readResponseBody(e){let t=await e.text();if(t.length===0)return null;if((e.headers.get(`content-type`)??``).includes(`application/json`))try{return JSON.parse(t)}catch{return t}return t}export{OpenApiConnectionClient};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { OpenApiParameter, OpenApiRequestBody } from "#runtime/connections/openapi-schema.js";
|
|
2
|
+
import type { SecurityPlacement } from "#runtime/connections/openapi-security.js";
|
|
3
|
+
/** HTTP methods OpenAPI defines operations for. */
|
|
4
|
+
export declare const HTTP_METHODS: readonly ["get", "put", "post", "delete", "patch", "head", "options"];
|
|
5
|
+
export type HttpMethod = (typeof HTTP_METHODS)[number];
|
|
6
|
+
/** Internal descriptor for one resolved OpenAPI operation. */
|
|
7
|
+
export interface OpenApiOperation {
|
|
8
|
+
readonly toolName: string;
|
|
9
|
+
readonly method: HttpMethod;
|
|
10
|
+
readonly pathTemplate: string;
|
|
11
|
+
readonly description: string;
|
|
12
|
+
readonly parameters: readonly OpenApiParameter[];
|
|
13
|
+
readonly requestBody: OpenApiRequestBody | undefined;
|
|
14
|
+
readonly inputSchema: Record<string, unknown>;
|
|
15
|
+
readonly security: SecurityPlacement | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Derives a tool name for an operation that is legal for model providers.
|
|
19
|
+
*
|
|
20
|
+
* Provider tool-name rules (Anthropic, OpenAI) only permit
|
|
21
|
+
* `[a-zA-Z0-9_-]`, so an `operationId` is sanitized rather than used
|
|
22
|
+
* verbatim — characters like dots or slashes (common in real specs)
|
|
23
|
+
* would otherwise be rejected and break the whole connection. Operations
|
|
24
|
+
* without an `operationId` get a deterministic `<method>_<path>` name.
|
|
25
|
+
*/
|
|
26
|
+
export declare function operationName(operation: Record<string, unknown>, method: HttpMethod, pathTemplate: string): string;
|
|
27
|
+
/** Disambiguates a tool name against names already used in the same connection. */
|
|
28
|
+
export declare function uniqueName(name: string, used: Set<string>): string;
|
|
29
|
+
/** Picks a human description for an operation, preferring `summary` over `description`. */
|
|
30
|
+
export declare function operationDescription(operation: Record<string, unknown>): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const HTTP_METHODS=[`get`,`put`,`post`,`delete`,`patch`,`head`,`options`];function operationName(e,t,n){if(typeof e.operationId==`string`&&e.operationId.length>0){let t=sanitizeToolName(e.operationId);if(t.length>0)return t}return sanitizeToolName(`${t}_${n.replace(/[{}]/g,``).replace(/[^a-zA-Z0-9]+/g,`_`).replace(/^_+|_+$/g,``)}`)}function sanitizeToolName(e){return e.replace(/[^a-zA-Z0-9_-]+/g,`_`).replace(/^[_-]+|[_-]+$/g,``).slice(0,64)}function uniqueName(e,t){let n=e,r=2;for(;t.has(n);)n=`${e}_${r}`,r+=1;return t.add(n),n}function operationDescription(e){return typeof e.summary==`string`&&e.summary.length>0?e.summary:typeof e.description==`string`?e.description:``}export{HTTP_METHODS,operationDescription,operationName,uniqueName};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** A path, query, header, or cookie parameter resolved from an operation. */
|
|
2
|
+
export interface OpenApiParameter {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly location: "path" | "query" | "header" | "cookie";
|
|
5
|
+
readonly required: boolean;
|
|
6
|
+
readonly schema: Record<string, unknown>;
|
|
7
|
+
readonly description?: string;
|
|
8
|
+
}
|
|
9
|
+
/** A request body resolved from an operation. */
|
|
10
|
+
export interface OpenApiRequestBody {
|
|
11
|
+
readonly required: boolean;
|
|
12
|
+
readonly contentType: string;
|
|
13
|
+
readonly schema: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Builds the combined JSON Schema the model fills in: each path, query,
|
|
17
|
+
* and header parameter becomes a top-level property; the request body
|
|
18
|
+
* (when present) is nested under `body`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildInputSchema(parameters: readonly OpenApiParameter[], requestBody: OpenApiRequestBody | undefined): Record<string, unknown>;
|
|
21
|
+
/** Resolves a single `$ref` node one hop; returns the node unchanged otherwise. */
|
|
22
|
+
export declare function deref(document: Record<string, unknown>, node: Record<string, unknown>): unknown;
|
|
23
|
+
/**
|
|
24
|
+
* Deeply resolves local `$ref` pointers in a JSON Schema, truncating
|
|
25
|
+
* `$ref` cycles and over-deep nesting so the result stays finite and
|
|
26
|
+
* serializable.
|
|
27
|
+
*
|
|
28
|
+
* Truncation only ever happens at an **object** node — which is always a
|
|
29
|
+
* schema position — by replacing it with an empty schema (`{}`). Scalars
|
|
30
|
+
* (`type` strings, `required` entries, `enum` values) pass through
|
|
31
|
+
* unchanged and arrays are always preserved as arrays, so array-valued
|
|
32
|
+
* keywords like `oneOf`/`anyOf`/`allOf` and `type: [..., "null"]` keep
|
|
33
|
+
* their shape. This keeps the output valid JSON Schema (draft 2020-12)
|
|
34
|
+
* for strict model providers, while the depth bound prevents recursive
|
|
35
|
+
* specs (e.g. Notion blocks) from expanding without limit.
|
|
36
|
+
*/
|
|
37
|
+
export declare function derefSchema(document: Record<string, unknown>, node: unknown, depth?: number, seen?: ReadonlySet<string>): unknown;
|
|
38
|
+
/** Narrows `unknown` to a readonly array, used pervasively when duck-typing spec nodes. */
|
|
39
|
+
export declare function isArray(value: unknown): value is readonly unknown[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isObject}from"#shared/guards.js";function buildInputSchema(e,t){let n={},r=[];for(let t of e)n[t.name]=t.description===void 0?t.schema:{...t.schema,description:t.description},t.required&&r.push(t.name);t!==void 0&&(n.body=t.schema,t.required&&r.push(`body`));let i={type:`object`,properties:n};return r.length>0&&(i.required=r),i}function deref(e,t){return typeof t.$ref==`string`?resolveRef(e,t.$ref)??{}:t}function derefSchema(t,n,r=0,i=new Set){if(isArray(n))return n.map(e=>derefSchema(t,e,r+1,i));if(!isObject(n))return n;if(r>12)return{};if(typeof n.$ref==`string`){if(i.has(n.$ref))return{};let e=resolveRef(t,n.$ref);return e===void 0?{}:derefSchema(t,e,r+1,new Set([...i,n.$ref]))}let a={};for(let[e,o]of Object.entries(n))a[e]=derefSchema(t,o,r+1,i);return normalizeSchemaType(a),normalizeNullable(a),a}const VALID_JSON_SCHEMA_TYPES=new Set([`string`,`number`,`integer`,`boolean`,`object`,`array`,`null`]);function normalizeSchemaType(e){let n=e.type;if(typeof n==`string`&&!VALID_JSON_SCHEMA_TYPES.has(n))delete e.type;else if(isArray(n)){let r=n.filter(e=>typeof e==`string`&&VALID_JSON_SCHEMA_TYPES.has(e));r.length===0?delete e.type:e.type=r}}function normalizeNullable(e){if(!(`nullable`in e))return;let t=e.nullable===!0;if(delete e.nullable,!t)return;let n=e.type;typeof n==`string`?n!==`null`&&(e.type=[n,`null`]):isArray(n)?n.includes(`null`)||(e.type=[...n,`null`]):isArray(e.enum)&&!e.enum.includes(null)&&(e.enum=[...e.enum,null])}function resolveRef(t,n){if(!n.startsWith(`#/`))return;let r=n.slice(2).split(`/`).map(e=>e.replace(/~1/g,`/`).replace(/~0/g,`~`)),i=t;for(let t of r){if(!isObject(i))return;i=i[t]}return i}function isArray(e){return Array.isArray(e)}export{buildInputSchema,deref,derefSchema,isArray};
|