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.
- package/CHANGELOG.md +13 -0
- package/dist/docs/public/channels/README.md +3 -1
- package/dist/docs/public/hooks.md +76 -6
- package/dist/docs/public/skills.md +6 -1
- package/dist/docs/public/tools.md +4 -0
- package/dist/src/chunks/{dev-authored-source-watcher-BR7XYOW0.js → dev-authored-source-watcher-DKDaaPea.js} +1 -1
- package/dist/src/chunks/{host-Dxf7CGaE.js → host-Btr4S69C.js} +7 -7
- package/dist/src/chunks/paths-DZTgjrW-.js +88 -0
- package/dist/src/chunks/{prewarm-vAT2QBqc.js → prewarm-BELT37PI.js} +1 -1
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +3 -3
- package/dist/src/compiled/@ai-sdk/anthropic/_provider-utils.d.ts +15 -0
- package/dist/src/compiled/@ai-sdk/anthropic/index.d.ts +1259 -6
- package/dist/src/compiled/@ai-sdk/google/_provider-utils.d.ts +11 -0
- package/dist/src/compiled/@ai-sdk/google/index.d.ts +561 -6
- package/dist/src/compiled/@ai-sdk/google/index.js +4 -4
- package/dist/src/compiled/@ai-sdk/google/package.json +1 -1
- package/dist/src/compiled/@ai-sdk/openai/_provider-utils.d.ts +15 -0
- package/dist/src/compiled/@ai-sdk/openai/index.d.ts +1255 -6
- package/dist/src/compiled/@ai-sdk/openai/index.js +1 -1
- package/dist/src/compiled/@ai-sdk/openai/package.json +1 -1
- package/dist/src/compiler/normalize-skill.js +5 -1
- package/dist/src/compiler/workspace-resources.js +12 -5
- package/dist/src/context/hook-lifecycle.d.ts +8 -8
- package/dist/src/context/hook-lifecycle.js +88 -36
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/execution/skills/instructions.d.ts +8 -4
- package/dist/src/execution/skills/instructions.js +3 -2
- package/dist/src/harness/attachment-staging.js +5 -1
- package/dist/src/harness/tool-loop.js +11 -3
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/core.js +16 -1
- package/dist/src/internal/nitro/routes/workflow-route-helpers.d.ts +4 -4
- package/dist/src/internal/nitro/routes/workflow-route-helpers.js +6 -5
- package/dist/src/internal/nitro/routes/workflow-run-events.d.ts +1 -1
- package/dist/src/internal/nitro/routes/workflow-run-events.js +1 -1
- package/dist/src/internal/nitro/routes/workflow-run-steps.d.ts +1 -1
- package/dist/src/internal/nitro/routes/workflow-run-steps.js +1 -1
- package/dist/src/internal/nitro/routes/workflow-run.d.ts +1 -1
- package/dist/src/internal/nitro/routes/workflow-run.js +1 -1
- package/dist/src/internal/nitro/routes/workflow-runs.d.ts +1 -1
- package/dist/src/internal/nitro/routes/workflow-runs.js +1 -1
- package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +2 -0
- package/dist/src/public/definitions/hook.d.ts +11 -3
- package/dist/src/public/definitions/skill.d.ts +4 -3
- package/dist/src/public/definitions/skill.js +1 -1
- package/dist/src/public/next/index.d.ts +74 -0
- package/dist/src/public/next/index.js +150 -0
- package/dist/src/public/next/server.d.ts +6 -0
- package/dist/src/public/next/server.js +348 -0
- package/dist/src/public/next/vercel-json.d.ts +8 -0
- package/dist/src/public/next/vercel-json.js +76 -0
- package/dist/src/public/skills/index.d.ts +1 -1
- package/dist/src/public/skills/index.js +1 -1
- package/dist/src/public/tool-result-narrowing.d.ts +68 -0
- package/dist/src/public/tool-result-narrowing.js +58 -0
- package/dist/src/public/tools/index.d.ts +1 -0
- package/dist/src/public/tools/index.js +1 -0
- package/dist/src/runtime/resolve-connection.js +2 -0
- package/dist/src/runtime/resolve-tool.js +2 -0
- package/dist/src/shared/skill-definition.d.ts +20 -2
- package/dist/src/shared/skill-package.d.ts +43 -0
- package/dist/src/shared/skill-package.js +100 -0
- package/package.json +13 -3
- package/dist/src/chunks/paths-BcM3el4Q.js +0 -88
|
@@ -2,8 +2,10 @@ import { getAdapterKind } from "../channel/adapter.js";
|
|
|
2
2
|
import { emitRecoverableFailedTurn, emitTurnPreamble, getHarnessEmissionState, setHarnessEmissionState, } from "../harness/emission.js";
|
|
3
3
|
import { createLogger } from "../internal/logging.js";
|
|
4
4
|
import { toErrorMessage } from "../shared/errors.js";
|
|
5
|
+
import { normalizeSkillPackage, writeSkillPackageToSandbox, } from "../shared/skill-package.js";
|
|
6
|
+
import { formatAvailableSkillsSection } from "#execution/skills/instructions.js";
|
|
5
7
|
import { contextStorage } from "./container.js";
|
|
6
|
-
import { BundleKey, ChannelKey, ContinuationTokenKey, SessionIdKey, SessionPreparedKey, } from "./keys.js";
|
|
8
|
+
import { BundleKey, ChannelKey, ContinuationTokenKey, SandboxKey, SessionIdKey, SessionPreparedKey, } from "./keys.js";
|
|
7
9
|
const log = createLogger("hooks.lifecycle");
|
|
8
10
|
/**
|
|
9
11
|
* Runs the per-turn hook lifecycle inside the active ALS scope and
|
|
@@ -20,11 +22,12 @@ const log = createLogger("hooks.lifecycle");
|
|
|
20
22
|
*
|
|
21
23
|
* 2. `lifecycle.turn` — runs once per fresh delivery (the caller
|
|
22
24
|
* gates dispatch with `isHarnessBetweenTurns(session)`). Each
|
|
23
|
-
* hook may return `{ modelContext }`;
|
|
24
|
-
* concatenated in registry order
|
|
25
|
-
* the
|
|
26
|
-
*
|
|
27
|
-
* the
|
|
25
|
+
* hook may return `{ modelContext, skills }`; model-context
|
|
26
|
+
* contributions are concatenated in registry order and skills are
|
|
27
|
+
* materialized into the live sandbox before the model call. A
|
|
28
|
+
* thrown hook is caught here, the recoverable `turn.failed`
|
|
29
|
+
* cascade is emitted, and the dispatcher returns
|
|
30
|
+
* `kind: "turn-failed"` so the caller can park the session.
|
|
28
31
|
*
|
|
29
32
|
* Errors:
|
|
30
33
|
*
|
|
@@ -48,56 +51,63 @@ export async function dispatchHookLifecycle(input) {
|
|
|
48
51
|
turn: { sequence, turnId },
|
|
49
52
|
};
|
|
50
53
|
const modelContext = [];
|
|
54
|
+
let nextSession = input.session;
|
|
51
55
|
// ---------- lifecycle.session ----------
|
|
52
56
|
if (registry.session.length > 0 && ctx.get(SessionPreparedKey) !== true) {
|
|
53
57
|
// Set the flag BEFORE running the chain. A thrown session hook is
|
|
54
58
|
// a terminal session failure; the next turn must not retry.
|
|
55
59
|
ctx.set(SessionPreparedKey, true);
|
|
60
|
+
const stageSkills = [];
|
|
56
61
|
for (const entry of registry.session) {
|
|
57
62
|
const result = (await entry.handler(lifecycleInput, hookCtx));
|
|
58
63
|
if (result?.modelContext !== undefined && result.modelContext.length > 0) {
|
|
59
64
|
modelContext.push(...result.modelContext);
|
|
60
65
|
}
|
|
66
|
+
stageSkills.push(...normalizeLifecycleSkillResults(ctx, result));
|
|
61
67
|
}
|
|
68
|
+
nextSession = await materializeLifecycleSkills(ctx, nextSession, stageSkills);
|
|
62
69
|
}
|
|
63
70
|
// ---------- lifecycle.turn ----------
|
|
64
71
|
let preambleEmitted = false;
|
|
65
72
|
let preambleState = emissionState;
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
try {
|
|
74
|
+
const stageSkills = [];
|
|
75
|
+
for (const entry of registry.turn) {
|
|
68
76
|
const result = (await entry.handler(lifecycleInput, hookCtx));
|
|
69
77
|
if (result?.modelContext !== undefined && result.modelContext.length > 0) {
|
|
70
78
|
modelContext.push(...result.modelContext);
|
|
71
79
|
}
|
|
80
|
+
stageSkills.push(...normalizeLifecycleSkillResults(ctx, result));
|
|
72
81
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
code: "HOOK_TURN_FAILED",
|
|
82
|
-
message,
|
|
83
|
-
});
|
|
84
|
-
return {
|
|
85
|
-
kind: "turn-failed",
|
|
86
|
-
message,
|
|
87
|
-
nextSession: setHarnessEmissionState(input.session, failedState),
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
catch (cascadeError) {
|
|
91
|
-
// An event hook subscribed to one of the failure-cascade
|
|
92
|
-
// events (eg. `events["turn.failed"]`) threw while we were
|
|
93
|
-
// emitting the recoverable failure. Re-throwing escalates to
|
|
94
|
-
// a terminal `session.failed` via the runtime's outer error
|
|
95
|
-
// path. Document this as the bounded second-order behavior:
|
|
96
|
-
// when both a `lifecycle.turn` and a `turn.failed` event hook
|
|
97
|
-
// throw, the session ends.
|
|
98
|
-
log.error(`Event hook threw while emitting the turn.failed cascade for hook "${entry.slug}" — escalating to session.failed.`, { error: toErrorMessage(cascadeError) });
|
|
99
|
-
throw cascadeError;
|
|
82
|
+
nextSession = await materializeLifecycleSkills(ctx, nextSession, stageSkills);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = toErrorMessage(error);
|
|
86
|
+
try {
|
|
87
|
+
if (!preambleEmitted) {
|
|
88
|
+
preambleState = await emitTurnPreamble(emit, { message: input.input.message }, emissionState, input.runtimeIdentity);
|
|
89
|
+
preambleEmitted = true;
|
|
100
90
|
}
|
|
91
|
+
const failedState = await emitRecoverableFailedTurn(emit, preambleState, {
|
|
92
|
+
code: "HOOK_TURN_FAILED",
|
|
93
|
+
message,
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
kind: "turn-failed",
|
|
97
|
+
message,
|
|
98
|
+
nextSession: setHarnessEmissionState(nextSession, failedState),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (cascadeError) {
|
|
102
|
+
// An event hook subscribed to one of the failure-cascade events
|
|
103
|
+
// (eg. `events["turn.failed"]`) threw while we were emitting the
|
|
104
|
+
// recoverable failure. Re-throwing escalates to a terminal
|
|
105
|
+
// `session.failed` via the runtime's outer error path. Document
|
|
106
|
+
// this as the bounded second-order behavior: when both a
|
|
107
|
+
// `lifecycle.turn` and a `turn.failed` event hook throw, the
|
|
108
|
+
// session ends.
|
|
109
|
+
log.error("Event hook threw while emitting the turn.failed cascade for a lifecycle.turn failure — escalating to session.failed.", { error: toErrorMessage(cascadeError) });
|
|
110
|
+
throw cascadeError;
|
|
101
111
|
}
|
|
102
112
|
}
|
|
103
113
|
const channelContext = input.input.modelContext ?? [];
|
|
@@ -107,7 +117,49 @@ export async function dispatchHookLifecycle(input) {
|
|
|
107
117
|
input: mergedModelContext.length > 0
|
|
108
118
|
? { ...input.input, modelContext: mergedModelContext }
|
|
109
119
|
: input.input,
|
|
110
|
-
nextSession
|
|
120
|
+
nextSession,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function normalizeLifecycleSkillResults(ctx, result) {
|
|
124
|
+
if (result?.skills === undefined || result.skills.length === 0) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const authoredSkillNames = new Set(ctx.require(BundleKey).resolvedAgent.skills.map((skill) => skill.name));
|
|
128
|
+
const dynamicSkills = new Map();
|
|
129
|
+
for (const skill of result.skills) {
|
|
130
|
+
const normalized = normalizeSkillPackage(skill);
|
|
131
|
+
if (authoredSkillNames.has(normalized.name)) {
|
|
132
|
+
throw new Error(`Hook-contributed skill "${normalized.name}" conflicts with an authored skill.`);
|
|
133
|
+
}
|
|
134
|
+
dynamicSkills.set(normalized.name, normalized);
|
|
135
|
+
}
|
|
136
|
+
return [...dynamicSkills.values()];
|
|
137
|
+
}
|
|
138
|
+
async function materializeLifecycleSkills(ctx, session, skills) {
|
|
139
|
+
if (skills.length === 0) {
|
|
140
|
+
return session;
|
|
141
|
+
}
|
|
142
|
+
const dedupedSkills = new Map(skills.map((skill) => [skill.name, skill]));
|
|
143
|
+
const sandbox = await ctx.require(SandboxKey).get();
|
|
144
|
+
if (sandbox === null) {
|
|
145
|
+
throw new Error("Dynamic skills require a sandbox for the current agent.");
|
|
146
|
+
}
|
|
147
|
+
for (const skill of dedupedSkills.values()) {
|
|
148
|
+
await writeSkillPackageToSandbox({ sandbox, skill });
|
|
149
|
+
}
|
|
150
|
+
const announcement = formatAvailableSkillsSection([...dedupedSkills.values()]);
|
|
151
|
+
if (announcement === null) {
|
|
152
|
+
return session;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
...session,
|
|
156
|
+
history: [
|
|
157
|
+
...session.history,
|
|
158
|
+
{
|
|
159
|
+
role: "system",
|
|
160
|
+
content: announcement,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
111
163
|
};
|
|
112
164
|
}
|
|
113
165
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"../../chunks/paths-
|
|
1
|
+
import{n as e}from"../../chunks/paths-DZTgjrW-.js";import{loadDevelopmentEnvironmentFiles as t}from"../../cli/dev/environment.js";import{a as n,n as r,t as i}from"../../chunks/client-CKsU8Li3.js";import{n as a}from"../../chunks/host-Btr4S69C.js";import{discoverAndImportSuites as o,discoverSuiteFiles as s,importSuiteFile as c}from"../runner/discover.js";import{executeSuite as l}from"../runner/execute-suite.js";import{ConsoleReporter as u}from"../runner/reporters/console.js";var d=n();function f(e,t){e.command(`eval`).description(`Run eval suites against an Ash agent.`).option(`--suite <id...>`,`Suite IDs to run (repeatable)`).option(`--all`,`Run all discovered suites`).option(`--url <url>`,`Remote agent URL (skip local host startup)`).option(`--timeout <ms>`,`Per-case timeout in milliseconds`).option(`--max-concurrency <n>`,`Max concurrent case executions per suite`).option(`--json`,`Output results as JSON`).option(`--list-suites`,`List discovered suites and exit`).option(`--skip-report`,`Skip suite-defined reporters (e.g. Braintrust)`).action(async e=>{await p(e,t)})}async function p(n,r){let i=e();if(t(i),n.listSuites){await y(i,r);return}let s=n.suite,c=await o(i,s);if(c.length===0){s&&s.length>0?r.error(`No suites found matching: ${s.join(`, `)}`):r.error(`No eval suites found. Create suite files under evals/ with the *.eval.ts extension.`),process.exitCode=1;return}let u,d;n.url?d={kind:`remote`,url:n.url}:(u=await a(i,{host:`127.0.0.1`,port:0}),d={kind:`local`,url:u.url});let f=m(d);try{let e=[];for(let t of c){let r=_(t,n),a=v(r,{json:n.json===!0,skipReport:n.skipReport===!0}),o=await l({suite:r,target:d,reporters:a,appRoot:i,client:f});e.push(o)}n.json&&r.log(JSON.stringify(e,null,2)),e.some(e=>e.errored>0)&&(process.exitCode=1)}finally{u&&await u.close()}process.exit(process.exitCode??0)}function m(e){if(e.kind===`local`)return new i({host:e.url});let t={},n=process.env.VERCEL_AUTOMATION_BYPASS_SECRET?.trim();return n&&(t[r]=n),new i({auth:h(),headers:Object.keys(t).length>0?t:void 0,host:e.url})}function h(){let e=process.env.ASH_EVAL_AUTH_TOKEN?.trim();return e?{bearer:e}:{bearer:g}}async function g(){try{let e=(await(0,d.getVercelOidcToken)()).trim();if(e.length>0)return e}catch{}return process.env.VERCEL_OIDC_TOKEN?.trim()??``}function _(e,t){let n=t.maxConcurrency?Number.parseInt(t.maxConcurrency,10):void 0,r=t.timeout?Number.parseInt(t.timeout,10):void 0;if(n===void 0&&r===void 0)return e;let i={...e};return n!==void 0&&(i.maxConcurrency=n),r!==void 0&&(i.timeoutMs=r),i}function v(e,t){let n=t.json?[]:[new u];return!t.skipReport&&e.reporters&&n.push(...e.reporters),n}async function y(e,t){let n=await s(e);if(n.length===0){t.log(`No eval suites found.`);return}t.log(`Found ${n.length} eval suite file(s):\n`);for(let r of n){let n=await c(e,r);t.log(` ${n.id}${n.description?` - ${n.description}`:``}`)}}export{f as registerEvalCommand,p as runEvalCommand};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
export interface AvailableSkillDescription {
|
|
2
|
+
readonly description: string;
|
|
3
|
+
readonly name: string;
|
|
4
|
+
}
|
|
2
5
|
/**
|
|
3
6
|
* Formats the "Available skills" system prompt section.
|
|
4
7
|
*
|
|
@@ -7,7 +10,8 @@ import type { ResolvedSkillDefinition } from "#runtime/types.js";
|
|
|
7
10
|
* has them from the `load_skill` tool result. This keeps the system
|
|
8
11
|
* prompt identical across the entire session, preserving prompt caching.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
* agent's static instructions
|
|
13
|
+
* Authored skills call this at graph resolution time so the section is
|
|
14
|
+
* part of the turn agent's static instructions. Hook-contributed skills
|
|
15
|
+
* reuse the same formatter for durable history announcements.
|
|
12
16
|
*/
|
|
13
|
-
export declare function formatAvailableSkillsSection(skills: readonly
|
|
17
|
+
export declare function formatAvailableSkillsSection(skills: readonly AvailableSkillDescription[]): string | null;
|
|
@@ -7,8 +7,9 @@ import { WORKSPACE_ROOT } from "#runtime/workspace/types.js";
|
|
|
7
7
|
* has them from the `load_skill` tool result. This keeps the system
|
|
8
8
|
* prompt identical across the entire session, preserving prompt caching.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* agent's static instructions
|
|
10
|
+
* Authored skills call this at graph resolution time so the section is
|
|
11
|
+
* part of the turn agent's static instructions. Hook-contributed skills
|
|
12
|
+
* reuse the same formatter for durable history announcements.
|
|
12
13
|
*/
|
|
13
14
|
export function formatAvailableSkillsSection(skills) {
|
|
14
15
|
if (skills.length === 0) {
|
|
@@ -84,7 +84,11 @@ export async function stageAttachmentsToSandbox(message) {
|
|
|
84
84
|
const adapter = container.get(ChannelKey);
|
|
85
85
|
const adapterCtx = adapter
|
|
86
86
|
? buildAdapterContext(adapter, container)
|
|
87
|
-
: {
|
|
87
|
+
: {
|
|
88
|
+
ctx: container,
|
|
89
|
+
state: {},
|
|
90
|
+
session: buildSessionHandle(container),
|
|
91
|
+
};
|
|
88
92
|
return stageAttachmentsForAdapter(message, sandbox, adapterCtx);
|
|
89
93
|
}
|
|
90
94
|
/**
|
|
@@ -299,10 +299,18 @@ export function createToolLoopHarness(config) {
|
|
|
299
299
|
// bloating every future step boundary.
|
|
300
300
|
const hydratedMessages = await hydrateSandboxAttachments(messages);
|
|
301
301
|
const ephemeralContext = stepInput.input?.modelContext;
|
|
302
|
-
// AI SDK rejects role:"system" in `messages` — route
|
|
303
|
-
// to `instructions` instead.
|
|
302
|
+
// AI SDK rejects role:"system" in `messages` — route durable history
|
|
303
|
+
// and ephemeral context system entries to `instructions` instead.
|
|
304
304
|
const systemMessages = [];
|
|
305
305
|
const nonSystemMessages = [];
|
|
306
|
+
for (const entry of hydratedMessages) {
|
|
307
|
+
if (entry.role === "system") {
|
|
308
|
+
systemMessages.push(entry);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
nonSystemMessages.push(entry);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
306
314
|
if (ephemeralContext !== undefined) {
|
|
307
315
|
for (const entry of ephemeralContext) {
|
|
308
316
|
if (entry.role === "system") {
|
|
@@ -334,7 +342,7 @@ export function createToolLoopHarness(config) {
|
|
|
334
342
|
tools: effectiveTools,
|
|
335
343
|
};
|
|
336
344
|
const agent = new ToolLoopAgent(agentSettings);
|
|
337
|
-
const modelMessages = nonSystemMessages
|
|
345
|
+
const modelMessages = nonSystemMessages;
|
|
338
346
|
let result;
|
|
339
347
|
const executeModelCall = async () => {
|
|
340
348
|
if (emit) {
|
|
@@ -6,7 +6,7 @@ import { ASH_PACKAGE_NAME } from "#package-name.js";
|
|
|
6
6
|
let cachedPackageInfo;
|
|
7
7
|
// The package build stamps the published version into `dist` so bundled
|
|
8
8
|
// deployments can still report package metadata without resolving package.json.
|
|
9
|
-
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.
|
|
9
|
+
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.22.0";
|
|
10
10
|
const BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER = "__ASH_PACKAGE_VERSION_PLACEHOLDER__";
|
|
11
11
|
const WORKFLOW_MODULE_ALIASES = {
|
|
12
12
|
"workflow/api": "src/compiled/@workflow/core/runtime.js",
|
|
@@ -118,7 +118,7 @@ export function normalizeInstructionsDefinition(value, message) {
|
|
|
118
118
|
*/
|
|
119
119
|
export function normalizeSkillDefinition(value, message) {
|
|
120
120
|
const record = expectObjectRecord(value, message);
|
|
121
|
-
expectOnlyKnownKeys(record, ["description", "license", "markdown", "metadata"], message);
|
|
121
|
+
expectOnlyKnownKeys(record, ["description", "files", "license", "markdown", "metadata"], message);
|
|
122
122
|
const definition = {
|
|
123
123
|
description: expectString(record.description, message),
|
|
124
124
|
markdown: expectString(record.markdown, message),
|
|
@@ -131,8 +131,23 @@ export function normalizeSkillDefinition(value, message) {
|
|
|
131
131
|
if (metadata !== undefined) {
|
|
132
132
|
definition.metadata = metadata;
|
|
133
133
|
}
|
|
134
|
+
if (record.files !== undefined) {
|
|
135
|
+
definition.files = normalizeSkillFiles(record.files, message);
|
|
136
|
+
}
|
|
134
137
|
return definition;
|
|
135
138
|
}
|
|
139
|
+
function normalizeSkillFiles(value, message) {
|
|
140
|
+
const files = expectObjectRecord(value, message);
|
|
141
|
+
const normalized = {};
|
|
142
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
143
|
+
if (typeof content === "string" || content instanceof Uint8Array) {
|
|
144
|
+
normalized[filePath] = content;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
throw new Error(`${message} Expected skill file "${filePath}" to be a string or Uint8Array.`);
|
|
148
|
+
}
|
|
149
|
+
return normalized;
|
|
150
|
+
}
|
|
136
151
|
/**
|
|
137
152
|
* Normalizes one authored schedule definition into the canonical internal
|
|
138
153
|
* shape.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { H3Event } from "nitro";
|
|
2
|
-
export declare const WORKFLOW_RUNS_API_ROUTE_PATH = "/
|
|
3
|
-
export declare const WORKFLOW_RUN_ROUTE_PATTERN = "/
|
|
4
|
-
export declare const WORKFLOW_RUN_STEPS_ROUTE_PATTERN = "/
|
|
5
|
-
export declare const WORKFLOW_RUN_EVENTS_ROUTE_PATTERN = "/
|
|
2
|
+
export declare const WORKFLOW_RUNS_API_ROUTE_PATH = "/ash/v1/runs";
|
|
3
|
+
export declare const WORKFLOW_RUN_ROUTE_PATTERN = "/ash/v1/runs/:runId";
|
|
4
|
+
export declare const WORKFLOW_RUN_STEPS_ROUTE_PATTERN = "/ash/v1/runs/:runId/steps";
|
|
5
|
+
export declare const WORKFLOW_RUN_EVENTS_ROUTE_PATTERN = "/ash/v1/runs/:runId/events";
|
|
6
6
|
declare const WORKFLOW_RUN_STATUSES: readonly ["pending", "running", "completed", "failed", "cancelled"];
|
|
7
7
|
export type WorkflowRunStatus = (typeof WORKFLOW_RUN_STATUSES)[number];
|
|
8
8
|
export interface PaginationQuery {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
1
|
+
import { ASH_ROUTE_PREFIX } from "#protocol/routes.js";
|
|
2
|
+
export const WORKFLOW_RUNS_API_ROUTE_PATH = `${ASH_ROUTE_PREFIX}/runs`;
|
|
3
|
+
export const WORKFLOW_RUN_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/runs/:runId`;
|
|
4
|
+
export const WORKFLOW_RUN_STEPS_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/runs/:runId/steps`;
|
|
5
|
+
export const WORKFLOW_RUN_EVENTS_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/runs/:runId/events`;
|
|
5
6
|
const MAX_PAGE_LIMIT = 1000;
|
|
6
7
|
const WORKFLOW_RUN_STATUSES = ["pending", "running", "completed", "failed", "cancelled"];
|
|
7
8
|
function createWorkflowApiResponse(payload, status) {
|
|
@@ -119,7 +120,7 @@ export function resolveRunIdFromNitroEvent(event) {
|
|
|
119
120
|
return routeRunId;
|
|
120
121
|
}
|
|
121
122
|
const path = new URL(event.req.url).pathname;
|
|
122
|
-
const pathMatch = path.match(/^\/
|
|
123
|
+
const pathMatch = path.match(/^\/ash\/v1\/runs\/([^/]+)(?:\/.*)?$/);
|
|
123
124
|
if (pathMatch === null) {
|
|
124
125
|
return null;
|
|
125
126
|
}
|
|
@@ -7,7 +7,7 @@ export declare function handleWorkflowRunEventsRequest(input: {
|
|
|
7
7
|
readonly runId: string;
|
|
8
8
|
}): Promise<Response>;
|
|
9
9
|
/**
|
|
10
|
-
* Nitro route for `GET /
|
|
10
|
+
* Nitro route for `GET /ash/v1/runs/:runId/events`.
|
|
11
11
|
*/
|
|
12
12
|
declare const _default: (event: H3Event) => Promise<Response>;
|
|
13
13
|
export default _default;
|
|
@@ -27,7 +27,7 @@ export async function handleWorkflowRunEventsRequest(input) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Nitro route for `GET /
|
|
30
|
+
* Nitro route for `GET /ash/v1/runs/:runId/events`.
|
|
31
31
|
*/
|
|
32
32
|
export default async (event) => {
|
|
33
33
|
const runId = resolveRunIdFromNitroEvent(event);
|
|
@@ -7,7 +7,7 @@ export declare function handleWorkflowRunStepsRequest(input: {
|
|
|
7
7
|
readonly runId: string;
|
|
8
8
|
}): Promise<Response>;
|
|
9
9
|
/**
|
|
10
|
-
* Nitro route for `GET /
|
|
10
|
+
* Nitro route for `GET /ash/v1/runs/:runId/steps`.
|
|
11
11
|
*/
|
|
12
12
|
declare const _default: (event: H3Event) => Promise<Response>;
|
|
13
13
|
export default _default;
|
|
@@ -27,7 +27,7 @@ export async function handleWorkflowRunStepsRequest(input) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Nitro route for `GET /
|
|
30
|
+
* Nitro route for `GET /ash/v1/runs/:runId/steps`.
|
|
31
31
|
*/
|
|
32
32
|
export default async (event) => {
|
|
33
33
|
const runId = resolveRunIdFromNitroEvent(event);
|
|
@@ -6,7 +6,7 @@ export declare function handleWorkflowRunRequest(input: {
|
|
|
6
6
|
runId: string;
|
|
7
7
|
}): Promise<Response>;
|
|
8
8
|
/**
|
|
9
|
-
* Nitro route for `GET /
|
|
9
|
+
* Nitro route for `GET /ash/v1/runs/:runId`.
|
|
10
10
|
*/
|
|
11
11
|
declare const _default: (event: H3Event) => Promise<Response>;
|
|
12
12
|
export default _default;
|
|
@@ -21,7 +21,7 @@ export async function handleWorkflowRunRequest(input) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Nitro route for `GET /
|
|
24
|
+
* Nitro route for `GET /ash/v1/runs/:runId`.
|
|
25
25
|
*/
|
|
26
26
|
export default async (event) => {
|
|
27
27
|
const runId = resolveRunIdFromNitroEvent(event);
|
|
@@ -4,7 +4,7 @@ import type { H3Event } from "nitro";
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function handleWorkflowRunsRequest(request: Request): Promise<Response>;
|
|
6
6
|
/**
|
|
7
|
-
* Nitro route for `GET /
|
|
7
|
+
* Nitro route for `GET /ash/v1/runs`.
|
|
8
8
|
*/
|
|
9
9
|
declare const _default: (event: H3Event) => Promise<Response>;
|
|
10
10
|
export default _default;
|
|
@@ -11,6 +11,7 @@ export const WORKFLOW_STEP_EXTERNAL_PACKAGES = ["@mongodb-js/zstd", "node-liblzm
|
|
|
11
11
|
* Nitro performs the final bundling/tracing pass for hosted output.
|
|
12
12
|
*/
|
|
13
13
|
export const WORKFLOW_BUILDER_DEFERRED_PACKAGES = ["@chat-adapter/slack", "chat"];
|
|
14
|
+
const WORKFLOW_FUNCTION_NODE_OPTIONS = "--experimental-require-module";
|
|
14
15
|
/**
|
|
15
16
|
* Builds the environment block every generated Vercel workflow function needs.
|
|
16
17
|
*/
|
|
@@ -19,6 +20,7 @@ export function createWorkflowFunctionEnvironment(environment) {
|
|
|
19
20
|
if (isRecord(environment)) {
|
|
20
21
|
Object.assign(nextEnvironment, environment);
|
|
21
22
|
}
|
|
23
|
+
nextEnvironment.NODE_OPTIONS = WORKFLOW_FUNCTION_NODE_OPTIONS;
|
|
22
24
|
return nextEnvironment;
|
|
23
25
|
}
|
|
24
26
|
function isRecord(value) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ModelMessage } from "ai";
|
|
2
2
|
import type { ContextAccessor } from "../../context/key.js";
|
|
3
3
|
import type { HandleMessageStreamEvent } from "../../protocol/message.js";
|
|
4
|
+
import type { NamedSkillDefinition } from "./skill.js";
|
|
4
5
|
/**
|
|
5
6
|
* Context passed to every hook handler.
|
|
6
7
|
*
|
|
@@ -45,15 +46,22 @@ export interface LifecycleHookInput {
|
|
|
45
46
|
* `lifecycle.turn`).
|
|
46
47
|
*
|
|
47
48
|
* `modelContext` messages are appended to the next model call's message
|
|
48
|
-
* list and never written to durable session history.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
* list and never written to durable session history. `skills` are
|
|
50
|
+
* materialized into the live sandbox and announced through durable
|
|
51
|
+
* system history. Each lifecycle key runs in registry order;
|
|
52
|
+
* contributions are concatenated session-then-turn before the harness's
|
|
53
|
+
* first model call.
|
|
51
54
|
*
|
|
52
55
|
* Returning `void` (or `undefined`) means "no opinion" — the turn
|
|
53
56
|
* proceeds with no additional model context from this hook.
|
|
54
57
|
*/
|
|
55
58
|
export interface LifecycleHookResult {
|
|
56
59
|
readonly modelContext?: readonly ModelMessage[];
|
|
60
|
+
/**
|
|
61
|
+
* Skill packages to materialize into the live durable sandbox before the
|
|
62
|
+
* next model call. Authored skill names cannot be overwritten.
|
|
63
|
+
*/
|
|
64
|
+
readonly skills?: readonly NamedSkillDefinition[];
|
|
57
65
|
}
|
|
58
66
|
/**
|
|
59
67
|
* Lifecycle hook signature shared by `lifecycle.session` and
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NamedSkillDefinition, SkillFileContent, SkillPackageDefinition } from "#shared/skill-definition.js";
|
|
2
|
+
export type { NamedSkillDefinition, SkillFileContent, SkillPackageDefinition };
|
|
2
3
|
/**
|
|
3
4
|
* Public definition for skill instructions lowered from authored markdown
|
|
4
5
|
* or authored directly in TypeScript via {@link defineSkill}.
|
|
@@ -6,9 +7,9 @@ import type { InternalSkillDefinition } from "#shared/skill-definition.js";
|
|
|
6
7
|
* Identity is derived from the file path under `agent/skills/`; authored
|
|
7
8
|
* definitions do not carry a `name` field.
|
|
8
9
|
*/
|
|
9
|
-
export type SkillDefinition =
|
|
10
|
+
export type SkillDefinition = SkillPackageDefinition;
|
|
10
11
|
/**
|
|
11
12
|
* Defines a skill in TypeScript using the same shape discovery produces from
|
|
12
|
-
* markdown.
|
|
13
|
+
* markdown, with optional package-relative sibling files.
|
|
13
14
|
*/
|
|
14
15
|
export declare function defineSkill<TSkill extends SkillDefinition>(definition: TSkill): TSkill;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
/**
|
|
3
|
+
* Private route namespace used when a Vercel deployment hosts Ash as a
|
|
4
|
+
* separate experimental service behind the Next.js app.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ASH_NEXT_SERVICE_PREFIX = "/_ash_internal/ash";
|
|
7
|
+
type ArrayElement<T> = T extends readonly (infer TElement)[] ? TElement : never;
|
|
8
|
+
type NextRewrites = Awaited<ReturnType<NonNullable<NextConfig["rewrites"]>>>;
|
|
9
|
+
/**
|
|
10
|
+
* Next.js rewrite rule used by {@link withAsh}.
|
|
11
|
+
*/
|
|
12
|
+
export type AshNextRewriteRule = ArrayElement<NextRewrites>;
|
|
13
|
+
/**
|
|
14
|
+
* Values accepted by Next.js `rewrites`.
|
|
15
|
+
*/
|
|
16
|
+
export type AshNextRewrites = NextRewrites;
|
|
17
|
+
/**
|
|
18
|
+
* Sectioned Next.js rewrite rules.
|
|
19
|
+
*/
|
|
20
|
+
export type AshNextRewriteSections = Extract<NextRewrites, {
|
|
21
|
+
readonly afterFiles?: AshNextRewriteRule[];
|
|
22
|
+
readonly beforeFiles?: AshNextRewriteRule[];
|
|
23
|
+
readonly fallback?: AshNextRewriteRule[];
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Next.js config object accepted by {@link withAsh}.
|
|
27
|
+
*/
|
|
28
|
+
export type AshNextConfig = NextConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Minimal structural shape for a Next.js config function.
|
|
31
|
+
*/
|
|
32
|
+
export type AshNextConfigFunction<TConfig extends AshNextConfig = AshNextConfig> = (phase: string, context: {
|
|
33
|
+
readonly defaultConfig: TConfig;
|
|
34
|
+
}) => TConfig | Promise<TConfig>;
|
|
35
|
+
/**
|
|
36
|
+
* Next.js config input accepted by {@link withAsh}.
|
|
37
|
+
*/
|
|
38
|
+
export type AshNextConfigInput<TConfig extends AshNextConfig = AshNextConfig> = AshNextConfigFunction<TConfig> | TConfig;
|
|
39
|
+
/**
|
|
40
|
+
* Options for {@link withAsh}.
|
|
41
|
+
*/
|
|
42
|
+
export interface WithAshOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Path to the Ash application root, relative to `process.cwd()` unless
|
|
45
|
+
* absolute. Defaults to the Next.js app root.
|
|
46
|
+
*/
|
|
47
|
+
readonly ashRoot?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Set to `false` to skip creating or updating `vercel.json`.
|
|
50
|
+
*
|
|
51
|
+
* By default `withAsh` ensures `vercel.json` contains `experimentalServices`
|
|
52
|
+
* for the Next.js app and Ash app.
|
|
53
|
+
*/
|
|
54
|
+
readonly configureVercelJson?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Private Vercel service prefix for the Ash deployment. This must match the
|
|
57
|
+
* Ash service's `routePrefix` in `vercel.json`.
|
|
58
|
+
*/
|
|
59
|
+
readonly servicePrefix?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Wraps a Next.js config so same-origin Ash endpoints proxy to a separate Ash
|
|
63
|
+
* service.
|
|
64
|
+
*
|
|
65
|
+
* In development, `withAsh` starts `ash dev --no-repl --port 0` for the Ash
|
|
66
|
+
* app and rewrites Ash protocol endpoints to that local URL. In production on
|
|
67
|
+
* Vercel, it rewrites to the private Ash service prefix configured in
|
|
68
|
+
* `vercel.json` using the deployment origin from `VERCEL_URL`. Outside Vercel
|
|
69
|
+
* production, it serves an existing `.output/server/index.mjs` Ash build on a
|
|
70
|
+
* stable local port when present; otherwise set `ASH_NEXT_PRODUCTION_ORIGIN` to
|
|
71
|
+
* the origin that serves the Ash service namespace.
|
|
72
|
+
*/
|
|
73
|
+
export declare function withAsh<TConfig extends AshNextConfig>(configOrFunction: AshNextConfigInput<TConfig>, options?: WithAshOptions): AshNextConfigFunction<TConfig>;
|
|
74
|
+
export {};
|