experimental-ash 0.25.1 → 0.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/src/channel/routes.d.ts +6 -1
  3. package/dist/src/channel/send.js +5 -2
  4. package/dist/src/channel/session-callback.d.ts +10 -0
  5. package/dist/src/channel/session-callback.js +65 -0
  6. package/dist/src/channel/types.d.ts +19 -0
  7. package/dist/src/chunks/{client-BShLWzR6.js → client-ZqNLLMZB.js} +3 -3
  8. package/dist/src/chunks/{compile-agent-CyP6FrL8.js → compile-agent-C4OrJW6C.js} +1 -1
  9. package/dist/src/chunks/{dev-authored-source-watcher-DIWfVUsu.js → dev-authored-source-watcher-PwAxalwl.js} +1 -1
  10. package/dist/src/chunks/host-F-DkwYJK.js +70 -0
  11. package/dist/src/chunks/paths-DnlVBqHu.js +85 -0
  12. package/dist/src/chunks/{token-BOkIxJeV.js → token-YW4VSeBB.js} +1 -1
  13. package/dist/src/chunks/types-BJSR0JNV.js +1 -0
  14. package/dist/src/cli/commands/info.js +1 -1
  15. package/dist/src/cli/dev/repl.js +3 -3
  16. package/dist/src/cli/run.js +1 -1
  17. package/dist/src/client/message-reducer.js +6 -0
  18. package/dist/src/context/keys.d.ts +1 -1
  19. package/dist/src/context/keys.js +1 -1
  20. package/dist/src/context/seed-keys.d.ts +5 -1
  21. package/dist/src/context/seed-keys.js +4 -0
  22. package/dist/src/evals/cli/eval.js +1 -1
  23. package/dist/src/execution/await-authorization-orchestrator.js +1 -1
  24. package/dist/src/execution/node-step.js +13 -0
  25. package/dist/src/execution/remote-agent-dispatch.d.ts +15 -0
  26. package/dist/src/execution/remote-agent-dispatch.js +79 -0
  27. package/dist/src/execution/runtime-context.js +4 -1
  28. package/dist/src/execution/session-callback-step.d.ts +16 -0
  29. package/dist/src/execution/session-callback-step.js +72 -0
  30. package/dist/src/execution/subagent-invocation.d.ts +16 -0
  31. package/dist/src/execution/subagent-invocation.js +16 -0
  32. package/dist/src/execution/subagent-tool.js +5 -8
  33. package/dist/src/execution/workflow-entry.js +21 -1
  34. package/dist/src/execution/workflow-steps.d.ts +6 -1
  35. package/dist/src/execution/workflow-steps.js +76 -25
  36. package/dist/src/harness/execute-tool.d.ts +3 -3
  37. package/dist/src/harness/runtime-actions.d.ts +1 -0
  38. package/dist/src/harness/runtime-actions.js +18 -1
  39. package/dist/src/internal/application/package.js +1 -1
  40. package/dist/src/protocol/message.d.ts +6 -0
  41. package/dist/src/protocol/message.js +1 -0
  42. package/dist/src/protocol/routes.d.ts +11 -0
  43. package/dist/src/protocol/routes.js +13 -0
  44. package/dist/src/public/channels/ash.js +25 -1
  45. package/dist/src/runtime/actions/keys.js +2 -0
  46. package/dist/src/runtime/actions/types.d.ts +47 -1
  47. package/dist/src/runtime/actions/types.js +23 -0
  48. package/dist/src/runtime/connections/callback-route.d.ts +1 -1
  49. package/dist/src/runtime/connections/callback-route.js +1 -1
  50. package/dist/src/runtime/connections/mcp-client.d.ts +1 -2
  51. package/dist/src/runtime/connections/mcp-client.js +69 -3
  52. package/dist/src/runtime/framework-channels/index.js +7 -2
  53. package/dist/src/runtime/session-callback-route.d.ts +6 -0
  54. package/dist/src/runtime/session-callback-route.js +87 -0
  55. package/package.json +1 -1
  56. package/dist/src/chunks/host-BxT35q6K.js +0 -70
  57. package/dist/src/chunks/paths-B2hLA0Fn.js +0 -85
  58. package/dist/src/chunks/types-CjIyrcYo.js +0 -1
@@ -180,6 +180,9 @@ export interface SubagentCalledStreamEvent {
180
180
  sessionId: string;
181
181
  sequence: number;
182
182
  name: string;
183
+ remote?: {
184
+ url: string;
185
+ };
183
186
  toolName: string;
184
187
  turnId: string;
185
188
  workflowId: string;
@@ -579,6 +582,9 @@ export declare function createSubagentCalledEvent(input: {
579
582
  readonly sessionId: string;
580
583
  readonly sequence: number;
581
584
  readonly name: string;
585
+ readonly remote?: {
586
+ readonly url: string;
587
+ };
582
588
  readonly toolName: string;
583
589
  readonly turnId: string;
584
590
  readonly workflowId: string;
@@ -200,6 +200,7 @@ export function createSubagentCalledEvent(input) {
200
200
  sessionId: input.sessionId,
201
201
  sequence: input.sequence,
202
202
  name: input.name,
203
+ remote: input.remote,
203
204
  toolName: input.toolName,
204
205
  turnId: input.turnId,
205
206
  workflowId: input.workflowId,
@@ -45,6 +45,13 @@ export declare const ASH_MESSAGE_STREAM_ROUTE_PATTERN = "/ash/v1/session/:sessio
45
45
  * exactly what the IdP needs to do.
46
46
  */
47
47
  export declare const ASH_CONNECTION_CALLBACK_ROUTE_PATTERN = "/ash/v1/connections/:name/callback/:token";
48
+ /**
49
+ * Stable framework-owned route pattern for terminal session callbacks.
50
+ *
51
+ * The `:token` segment is an unguessable workflow hook capability. The route
52
+ * is unauthenticated by design and resumes the matching parked runtime action.
53
+ */
54
+ export declare const ASH_CALLBACK_ROUTE_PATTERN = "/ash/v1/callback/:token";
48
55
  /**
49
56
  * Creates the stable framework-owned message stream route path for one session.
50
57
  */
@@ -64,3 +71,7 @@ export declare function createAshContinueSessionRoutePath(sessionId: string): st
64
71
  * `resumeHook(token, payload)`.
65
72
  */
66
73
  export declare function createAshConnectionCallbackRoutePath(name: string, token: string): string;
74
+ /**
75
+ * Creates the stable framework-owned terminal callback route path.
76
+ */
77
+ export declare function createAshCallbackRoutePath(token: string): string;
@@ -45,6 +45,13 @@ export const ASH_MESSAGE_STREAM_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/session/:se
45
45
  * exactly what the IdP needs to do.
46
46
  */
47
47
  export const ASH_CONNECTION_CALLBACK_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/connections/:name/callback/:token`;
48
+ /**
49
+ * Stable framework-owned route pattern for terminal session callbacks.
50
+ *
51
+ * The `:token` segment is an unguessable workflow hook capability. The route
52
+ * is unauthenticated by design and resumes the matching parked runtime action.
53
+ */
54
+ export const ASH_CALLBACK_ROUTE_PATTERN = `${ASH_ROUTE_PREFIX}/callback/:token`;
48
55
  /**
49
56
  * Creates the stable framework-owned message stream route path for one session.
50
57
  */
@@ -70,3 +77,9 @@ export function createAshContinueSessionRoutePath(sessionId) {
70
77
  export function createAshConnectionCallbackRoutePath(name, token) {
71
78
  return `${ASH_ROUTE_PREFIX}/connections/${encodeURIComponent(name)}/callback/${encodeURIComponent(token)}`;
72
79
  }
80
+ /**
81
+ * Creates the stable framework-owned terminal callback route path.
82
+ */
83
+ export function createAshCallbackRoutePath(token) {
84
+ return `${ASH_ROUTE_PREFIX}/callback/${encodeURIComponent(token)}`;
85
+ }
@@ -1,4 +1,5 @@
1
1
  import {} from "ai";
2
+ import { parseSessionCallback } from "#channel/session-callback.js";
2
3
  import { ASH_MESSAGE_STREAM_CONTENT_TYPE, ASH_MESSAGE_STREAM_FORMAT, ASH_MESSAGE_STREAM_VERSION, ASH_SESSION_ID_HEADER, ASH_STREAM_FORMAT_HEADER, ASH_STREAM_VERSION_HEADER, } from "#protocol/message.js";
3
4
  import { isInputResponse } from "#runtime/input/types.js";
4
5
  import { createUnauthorizedResponse } from "#public/channels/auth.js";
@@ -33,7 +34,9 @@ export function ashChannel(input) {
33
34
  const token = `ash:${crypto.randomUUID()}`;
34
35
  const session = await send(createSendPayload(body), {
35
36
  auth: sessionAuth,
37
+ callback: body.callback,
36
38
  continuationToken: token,
39
+ mode: body.mode,
37
40
  });
38
41
  return Response.json({
39
42
  continuationToken: session.continuationToken,
@@ -143,10 +146,16 @@ function parseCreateBody(payload) {
143
146
  const modelContext = parseClientContextField(payload.clientContext);
144
147
  if (modelContext instanceof Response)
145
148
  return modelContext;
149
+ const callback = parseCallbackField(payload.callback);
150
+ if (callback instanceof Response)
151
+ return callback;
152
+ const mode = parseModeField(payload.mode);
153
+ if (mode instanceof Response)
154
+ return mode;
146
155
  if (message === undefined) {
147
156
  return Response.json({ error: "Missing or empty 'message' field.", ok: false }, { status: 400 });
148
157
  }
149
- return { message, modelContext };
158
+ return { callback, message, mode, modelContext };
150
159
  }
151
160
  function parseContinueBody(payload) {
152
161
  const continuationToken = typeof payload.continuationToken === "string" && payload.continuationToken.length > 0
@@ -178,6 +187,21 @@ function createSendPayload(body) {
178
187
  }
179
188
  return { message: body.message, modelContext: body.modelContext };
180
189
  }
190
+ function parseCallbackField(value) {
191
+ if (value === undefined)
192
+ return undefined;
193
+ const parsed = parseSessionCallback(value);
194
+ if (parsed.ok)
195
+ return parsed.callback;
196
+ return Response.json({ error: parsed.message, ok: false }, { status: 400 });
197
+ }
198
+ function parseModeField(value) {
199
+ if (value === undefined)
200
+ return undefined;
201
+ if (value === "conversation" || value === "task")
202
+ return value;
203
+ return Response.json({ error: "Expected 'mode' to be either 'conversation' or 'task'.", ok: false }, { status: 400 });
204
+ }
181
205
  function parseMessageField(value) {
182
206
  if (value === undefined)
183
207
  return undefined;
@@ -6,6 +6,8 @@ export function getRuntimeActionRequestKey(action) {
6
6
  switch (action.kind) {
7
7
  case "load-skill":
8
8
  return `runtime-action:${action.kind}:${action.callId}`;
9
+ case "remote-agent-call":
10
+ return `subagent-call:${action.remoteAgentName}:${action.callId}`;
9
11
  case "subagent-call":
10
12
  return `subagent-call:${action.subagentName}:${action.callId}`;
11
13
  case "tool-call":
@@ -30,6 +30,23 @@ declare const runtimeSubagentCallActionRequestSchema: z.ZodObject<{
30
30
  nodeId: z.ZodString;
31
31
  subagentName: z.ZodString;
32
32
  }, z.core.$strict>;
33
+ /**
34
+ * Runtime-owned remote-agent-call request surfaced by a harness and executed
35
+ * later by workflow-backed runtime code.
36
+ */
37
+ export type RuntimeRemoteAgentCallActionRequest = z.infer<typeof runtimeRemoteAgentCallActionRequestSchema>;
38
+ /**
39
+ * Zod schema for one runtime-owned remote-agent-call action request.
40
+ */
41
+ export declare const runtimeRemoteAgentCallActionRequestSchema: z.ZodObject<{
42
+ callId: z.ZodString;
43
+ description: z.ZodString;
44
+ input: z.ZodType<import("../../shared/json.js").JsonObject, unknown, z.core.$ZodTypeInternals<import("../../shared/json.js").JsonObject, unknown>>;
45
+ kind: z.ZodLiteral<"remote-agent-call">;
46
+ name: z.ZodString;
47
+ nodeId: z.ZodString;
48
+ remoteAgentName: z.ZodString;
49
+ }, z.core.$strict>;
33
50
  /**
34
51
  * Runtime-owned action request surfaced by a harness.
35
52
  *
@@ -51,7 +68,36 @@ declare const runtimeLoadSkillActionRequestSchema: z.ZodObject<{
51
68
  * Harness-native capabilities such as `bash` do not cross the harness boundary
52
69
  * as runtime actions. Only runtime-executed requests use this taxonomy.
53
70
  */
54
- export type RuntimeActionRequest = RuntimeLoadSkillActionRequest | RuntimeSubagentCallActionRequest | RuntimeToolCallActionRequest;
71
+ export type RuntimeActionRequest = RuntimeLoadSkillActionRequest | RuntimeRemoteAgentCallActionRequest | RuntimeSubagentCallActionRequest | RuntimeToolCallActionRequest;
72
+ /**
73
+ * Zod schema for one runtime action request.
74
+ */
75
+ export declare const runtimeActionRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
76
+ callId: z.ZodString;
77
+ input: z.ZodType<import("../../shared/json.js").JsonObject, unknown, z.core.$ZodTypeInternals<import("../../shared/json.js").JsonObject, unknown>>;
78
+ kind: z.ZodLiteral<"load-skill">;
79
+ }, z.core.$strict>, z.ZodObject<{
80
+ callId: z.ZodString;
81
+ description: z.ZodString;
82
+ input: z.ZodType<import("../../shared/json.js").JsonObject, unknown, z.core.$ZodTypeInternals<import("../../shared/json.js").JsonObject, unknown>>;
83
+ kind: z.ZodLiteral<"remote-agent-call">;
84
+ name: z.ZodString;
85
+ nodeId: z.ZodString;
86
+ remoteAgentName: z.ZodString;
87
+ }, z.core.$strict>, z.ZodObject<{
88
+ callId: z.ZodString;
89
+ description: z.ZodString;
90
+ input: z.ZodType<import("../../shared/json.js").JsonObject, unknown, z.core.$ZodTypeInternals<import("../../shared/json.js").JsonObject, unknown>>;
91
+ kind: z.ZodLiteral<"subagent-call">;
92
+ name: z.ZodString;
93
+ nodeId: z.ZodString;
94
+ subagentName: z.ZodString;
95
+ }, z.core.$strict>, z.ZodObject<{
96
+ callId: z.ZodString;
97
+ input: z.ZodType<import("../../shared/json.js").JsonObject, unknown, z.core.$ZodTypeInternals<import("../../shared/json.js").JsonObject, unknown>>;
98
+ kind: z.ZodLiteral<"tool-call">;
99
+ toolName: z.ZodString;
100
+ }, z.core.$strict>], "kind">;
55
101
  /**
56
102
  * Runtime-owned authored tool-result projected back into a harness resume call.
57
103
  */
@@ -25,6 +25,20 @@ const runtimeSubagentCallActionRequestSchema = z
25
25
  subagentName: z.string(),
26
26
  })
27
27
  .strict();
28
+ /**
29
+ * Zod schema for one runtime-owned remote-agent-call action request.
30
+ */
31
+ export const runtimeRemoteAgentCallActionRequestSchema = z
32
+ .object({
33
+ callId: z.string(),
34
+ description: z.string(),
35
+ input: jsonObjectSchema,
36
+ kind: z.literal("remote-agent-call"),
37
+ name: z.string(),
38
+ nodeId: z.string(),
39
+ remoteAgentName: z.string(),
40
+ })
41
+ .strict();
28
42
  /**
29
43
  * Zod schema for one runtime-owned load-skill action request.
30
44
  */
@@ -35,6 +49,15 @@ const runtimeLoadSkillActionRequestSchema = z
35
49
  kind: z.literal("load-skill"),
36
50
  })
37
51
  .strict();
52
+ /**
53
+ * Zod schema for one runtime action request.
54
+ */
55
+ export const runtimeActionRequestSchema = z.discriminatedUnion("kind", [
56
+ runtimeLoadSkillActionRequestSchema,
57
+ runtimeRemoteAgentCallActionRequestSchema,
58
+ runtimeSubagentCallActionRequestSchema,
59
+ runtimeToolCallActionRequestSchema,
60
+ ]);
38
61
  /**
39
62
  * Zod schema for one runtime-owned authored tool-result action result.
40
63
  */
@@ -29,7 +29,7 @@ import type { ResolvedChannelDefinition } from "#runtime/types.js";
29
29
  * channel. The trailing method segment (`get` or `post`) keeps each
30
30
  * `(method, urlPath)` pair distinct in the channel registry.
31
31
  */
32
- export declare const HTTP_CONNECTION_CALLBACK_CHANNEL_NAME_PREFIX = ".well-known/ash/v1/connections/callback";
32
+ export declare const HTTP_CONNECTION_CALLBACK_CHANNEL_NAME_PREFIX = "ash/v1/connections/callback";
33
33
  /**
34
34
  * Returns the framework-shipped channel definitions that mount the
35
35
  * connection callback route at {@link ASH_CONNECTION_CALLBACK_ROUTE_PATTERN}.
@@ -30,7 +30,7 @@ import { buildAuthorizationCompletePage } from "#runtime/connections/authorizati
30
30
  * channel. The trailing method segment (`get` or `post`) keeps each
31
31
  * `(method, urlPath)` pair distinct in the channel registry.
32
32
  */
33
- export const HTTP_CONNECTION_CALLBACK_CHANNEL_NAME_PREFIX = ".well-known/ash/v1/connections/callback";
33
+ export const HTTP_CONNECTION_CALLBACK_CHANNEL_NAME_PREFIX = "ash/v1/connections/callback";
34
34
  /**
35
35
  * HTTP methods accepted by the connection callback route.
36
36
  *
@@ -13,8 +13,7 @@ export declare class McpConnectionClient implements ConnectionClient {
13
13
  constructor(connection: ResolvedConnectionDefinition);
14
14
  /**
15
15
  * Connects to the MCP server, trying Streamable HTTP first and
16
- * falling back to SSE if the server returns a 404 (indicating it
17
- * only supports the older SSE transport).
16
+ * falling back to SSE for transport-compatibility failures.
18
17
  *
19
18
  * Concurrent callers share the same connection promise to avoid
20
19
  * duplicate connections.
@@ -2,6 +2,7 @@ import { createMCPClient } from "#compiled/@ai-sdk/mcp/index.js";
2
2
  import { contextStorage } from "#context/container.js";
3
3
  import { readCachedToken, writeCachedToken } from "#runtime/connections/authorization-tokens.js";
4
4
  import { principalKey, resolveConnectionPrincipal } from "#runtime/connections/principal.js";
5
+ import { isObject } from "#shared/guards.js";
5
6
  /**
6
7
  * Wraps one `MCPClient` from `@ai-sdk/mcp` for a single connection.
7
8
  *
@@ -19,8 +20,7 @@ export class McpConnectionClient {
19
20
  }
20
21
  /**
21
22
  * Connects to the MCP server, trying Streamable HTTP first and
22
- * falling back to SSE if the server returns a 404 (indicating it
23
- * only supports the older SSE transport).
23
+ * falling back to SSE for transport-compatibility failures.
24
24
  *
25
25
  * Concurrent callers share the same connection promise to avoid
26
26
  * duplicate connections.
@@ -50,7 +50,10 @@ export class McpConnectionClient {
50
50
  transport: { type: "http", url, headers },
51
51
  });
52
52
  }
53
- catch {
53
+ catch (error) {
54
+ if (!isMcpHttpFallbackRetryableError(error)) {
55
+ throw error;
56
+ }
54
57
  return await createMCPClient({
55
58
  transport: { type: "sse", url, headers },
56
59
  });
@@ -137,6 +140,69 @@ export class McpConnectionClient {
137
140
  this.#tools = undefined;
138
141
  }
139
142
  }
143
+ /**
144
+ * Decides whether an error thrown while creating a streamable HTTP MCP
145
+ * client should trigger a fallback attempt against the legacy SSE
146
+ * transport.
147
+ *
148
+ * Per the MCP backwards-compatibility rules, clients should fall back
149
+ * to SSE when the streamable HTTP probe returns `400 Bad Request`,
150
+ * `404 Not Found`, or `405 Method Not Allowed`. All other failures
151
+ * (auth errors, network errors, server errors) should propagate so the
152
+ * caller sees the real problem instead of a misleading SSE failure.
153
+ *
154
+ * See: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#backwards-compatibility
155
+ */
156
+ function isMcpHttpFallbackRetryableError(error) {
157
+ const status = readHttpStatus(error);
158
+ return status === 400 || status === 404 || status === 405;
159
+ }
160
+ function readHttpStatus(error) {
161
+ for (const candidate of walkErrorChain(error)) {
162
+ if (!isObject(candidate)) {
163
+ continue;
164
+ }
165
+ const status = readStatusField(candidate);
166
+ if (status !== undefined) {
167
+ return status;
168
+ }
169
+ const response = candidate.response;
170
+ if (isObject(response)) {
171
+ const responseStatus = readStatusField(response);
172
+ if (responseStatus !== undefined) {
173
+ return responseStatus;
174
+ }
175
+ }
176
+ if (typeof candidate.message === "string") {
177
+ const match = /\bHTTP\s+(\d{3})\b/u.exec(candidate.message);
178
+ if (match?.[1] !== undefined) {
179
+ return Number(match[1]);
180
+ }
181
+ }
182
+ }
183
+ return undefined;
184
+ }
185
+ function readStatusField(value) {
186
+ if (typeof value.status === "number") {
187
+ return value.status;
188
+ }
189
+ if (typeof value.statusCode === "number") {
190
+ return value.statusCode;
191
+ }
192
+ return undefined;
193
+ }
194
+ function* walkErrorChain(error) {
195
+ let current = error;
196
+ const seen = new Set();
197
+ while (current !== undefined && current !== null && !seen.has(current)) {
198
+ seen.add(current);
199
+ yield current;
200
+ if (!isObject(current) || !("cause" in current)) {
201
+ return;
202
+ }
203
+ current = current.cause;
204
+ }
205
+ }
140
206
  /**
141
207
  * Returns `true` when a tool name passes the configured filter.
142
208
  */
@@ -1,6 +1,7 @@
1
1
  import { none, vercelOidc } from "#public/channels/auth.js";
2
2
  import { ashChannel } from "#public/channels/ash.js";
3
3
  import { getConnectionCallbackChannelDefinitions, getConnectionCallbackChannelNames, } from "#runtime/connections/callback-route.js";
4
+ import { getSessionCallbackChannelDefinitions, getSessionCallbackChannelNames, } from "#runtime/session-callback-route.js";
4
5
  const ASH_CHANNEL_NAME = "ash";
5
6
  export function getFrameworkChannelDefinitions() {
6
7
  const auth = resolveFrameworkAshAuth();
@@ -19,11 +20,15 @@ export function getFrameworkChannelDefinitions() {
19
20
  sourceKind: "module",
20
21
  });
21
22
  }
22
- result.push(...getConnectionCallbackChannelDefinitions());
23
+ result.push(...getConnectionCallbackChannelDefinitions(), ...getSessionCallbackChannelDefinitions());
23
24
  return result;
24
25
  }
25
26
  export function getAllFrameworkChannelNames() {
26
- return new Set([ASH_CHANNEL_NAME, ...getConnectionCallbackChannelNames()]);
27
+ return new Set([
28
+ ASH_CHANNEL_NAME,
29
+ ...getConnectionCallbackChannelNames(),
30
+ ...getSessionCallbackChannelNames(),
31
+ ]);
27
32
  }
28
33
  function resolveFrameworkAshAuth() {
29
34
  if (process.env.VERCEL) {
@@ -0,0 +1,6 @@
1
+ import type { RouteContext } from "#public/definitions/channel.js";
2
+ import type { ResolvedChannelDefinition } from "#runtime/types.js";
3
+ export declare const HTTP_SESSION_CALLBACK_CHANNEL_NAME_PREFIX = "ash/v1/callback";
4
+ export declare function getSessionCallbackChannelDefinitions(): readonly ResolvedChannelDefinition[];
5
+ export declare function getSessionCallbackChannelNames(): ReadonlySet<string>;
6
+ export declare function handleSessionCallbackRequest(request: Request, ctx: RouteContext): Promise<Response>;
@@ -0,0 +1,87 @@
1
+ import { resumeHook } from "#compiled/@workflow/core/runtime.js";
2
+ import { ASH_CALLBACK_ROUTE_PATTERN } from "#protocol/routes.js";
3
+ export const HTTP_SESSION_CALLBACK_CHANNEL_NAME_PREFIX = "ash/v1/callback";
4
+ const HANDLED_METHODS = ["POST"];
5
+ export function getSessionCallbackChannelDefinitions() {
6
+ return HANDLED_METHODS.map((method) => buildCallbackChannelDefinition(method));
7
+ }
8
+ export function getSessionCallbackChannelNames() {
9
+ return new Set(HANDLED_METHODS.map(channelNameForMethod));
10
+ }
11
+ function buildCallbackChannelDefinition(method) {
12
+ const name = channelNameForMethod(method);
13
+ return {
14
+ name,
15
+ method,
16
+ urlPath: ASH_CALLBACK_ROUTE_PATTERN,
17
+ fetch: handleSessionCallbackRequest,
18
+ logicalPath: `framework://channels/${name}`,
19
+ sourceId: `ash:framework:session-callback-${method.toLowerCase()}`,
20
+ sourceKind: "module",
21
+ };
22
+ }
23
+ function channelNameForMethod(method) {
24
+ return `${HTTP_SESSION_CALLBACK_CHANNEL_NAME_PREFIX}/${method.toLowerCase()}`;
25
+ }
26
+ export async function handleSessionCallbackRequest(request, ctx) {
27
+ const token = ctx.params.token;
28
+ if (typeof token !== "string" || token.length === 0) {
29
+ return Response.json({ error: "Missing callback token.", ok: false }, { status: 400 });
30
+ }
31
+ let body;
32
+ try {
33
+ body = await request.json();
34
+ }
35
+ catch {
36
+ return Response.json({ error: "Invalid JSON body.", ok: false }, { status: 400 });
37
+ }
38
+ const result = projectSessionCallbackResult(body);
39
+ if (result instanceof Response) {
40
+ return result;
41
+ }
42
+ try {
43
+ await resumeHook(token, {
44
+ kind: "runtime-action-result",
45
+ results: [result],
46
+ });
47
+ }
48
+ catch {
49
+ return Response.json({ error: "Session callback not pending.", ok: false }, { status: 404 });
50
+ }
51
+ return Response.json({ ok: true }, { status: 202 });
52
+ }
53
+ function projectSessionCallbackResult(value) {
54
+ if (value === null || typeof value !== "object") {
55
+ return Response.json({ error: "Expected a JSON object.", ok: false }, { status: 400 });
56
+ }
57
+ const payload = value;
58
+ if (typeof payload.callId !== "string" || payload.callId.length === 0) {
59
+ return Response.json({ error: "Missing callback callId.", ok: false }, { status: 400 });
60
+ }
61
+ if (typeof payload.subagentName !== "string" || payload.subagentName.length === 0) {
62
+ return Response.json({ error: "Missing callback subagentName.", ok: false }, { status: 400 });
63
+ }
64
+ if (payload.kind === "session.completed") {
65
+ return {
66
+ callId: payload.callId,
67
+ kind: "subagent-result",
68
+ output: typeof payload.output === "string" ? payload.output : "",
69
+ subagentName: payload.subagentName,
70
+ };
71
+ }
72
+ if (payload.kind === "session.failed") {
73
+ return {
74
+ callId: payload.callId,
75
+ isError: true,
76
+ kind: "subagent-result",
77
+ output: payload.error === undefined
78
+ ? {
79
+ code: "REMOTE_AGENT_FAILED",
80
+ message: "Remote agent failed.",
81
+ }
82
+ : payload.error,
83
+ subagentName: payload.subagentName,
84
+ };
85
+ }
86
+ return Response.json({ error: "Unsupported callback kind.", ok: false }, { status: 400 });
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "experimental-ash",
3
- "version": "0.25.1",
3
+ "version": "0.25.2",
4
4
  "bin": {
5
5
  "ash": "./bin/ash.js",
6
6
  "experimental-ash": "./bin/ash.js"