experimental-ash 0.25.0 → 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.
- package/CHANGELOG.md +12 -0
- package/dist/src/channel/routes.d.ts +6 -1
- package/dist/src/channel/send.js +5 -2
- package/dist/src/channel/session-callback.d.ts +10 -0
- package/dist/src/channel/session-callback.js +65 -0
- package/dist/src/channel/types.d.ts +19 -0
- package/dist/src/chunks/{client-BShLWzR6.js → client-ZqNLLMZB.js} +3 -3
- package/dist/src/chunks/{compile-agent-75wLLW-E.js → compile-agent-C4OrJW6C.js} +1 -1
- package/dist/src/chunks/{dev-authored-source-watcher-DqoJsDup.js → dev-authored-source-watcher-PwAxalwl.js} +1 -1
- package/dist/src/chunks/host-F-DkwYJK.js +70 -0
- package/dist/src/chunks/paths-DnlVBqHu.js +85 -0
- package/dist/src/chunks/{token-BOkIxJeV.js → token-YW4VSeBB.js} +1 -1
- package/dist/src/chunks/types-BJSR0JNV.js +1 -0
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/dev/repl.js +3 -3
- package/dist/src/cli/run.js +1 -1
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/client/message-reducer.js +6 -0
- package/dist/src/context/keys.d.ts +1 -1
- package/dist/src/context/keys.js +1 -1
- package/dist/src/context/seed-keys.d.ts +5 -1
- package/dist/src/context/seed-keys.js +4 -0
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/execution/await-authorization-orchestrator.d.ts +0 -2
- package/dist/src/execution/await-authorization-orchestrator.js +12 -20
- package/dist/src/execution/connection-auth-steps.js +1 -4
- package/dist/src/execution/node-step.js +13 -0
- package/dist/src/execution/remote-agent-dispatch.d.ts +15 -0
- package/dist/src/execution/remote-agent-dispatch.js +79 -0
- package/dist/src/execution/runtime-context.js +4 -1
- package/dist/src/execution/session-callback-step.d.ts +16 -0
- package/dist/src/execution/session-callback-step.js +72 -0
- package/dist/src/execution/subagent-invocation.d.ts +16 -0
- package/dist/src/execution/subagent-invocation.js +16 -0
- package/dist/src/execution/subagent-tool.js +5 -8
- package/dist/src/execution/turn-workflow.js +0 -1
- package/dist/src/execution/workflow-entry.js +21 -1
- package/dist/src/execution/workflow-steps.d.ts +6 -3
- package/dist/src/execution/workflow-steps.js +79 -37
- package/dist/src/harness/execute-tool.d.ts +3 -3
- package/dist/src/harness/runtime-actions.d.ts +1 -0
- package/dist/src/harness/runtime-actions.js +18 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/protocol/message.d.ts +6 -0
- package/dist/src/protocol/message.js +1 -0
- package/dist/src/protocol/routes.d.ts +11 -0
- package/dist/src/protocol/routes.js +13 -0
- package/dist/src/public/channels/ash.js +25 -1
- package/dist/src/runtime/actions/keys.js +2 -0
- package/dist/src/runtime/actions/types.d.ts +47 -1
- package/dist/src/runtime/actions/types.js +23 -0
- package/dist/src/runtime/connections/callback-route.d.ts +1 -1
- package/dist/src/runtime/connections/callback-route.js +1 -1
- package/dist/src/runtime/connections/mcp-client.d.ts +1 -2
- package/dist/src/runtime/connections/mcp-client.js +69 -3
- package/dist/src/runtime/framework-channels/index.js +7 -2
- package/dist/src/runtime/framework-tools/connection-search.d.ts +0 -46
- package/dist/src/runtime/framework-tools/connection-search.js +3 -78
- package/dist/src/runtime/framework-tools/connection-tools.d.ts +3 -6
- package/dist/src/runtime/framework-tools/connection-tools.js +6 -13
- package/dist/src/runtime/session-callback-route.d.ts +6 -0
- package/dist/src/runtime/session-callback-route.js +87 -0
- package/package.json +1 -1
- package/dist/src/chunks/host-JVy7fewA.js +0 -70
- package/dist/src/chunks/paths-DNjq5JOy.js +0 -85
- package/dist/src/chunks/types-CjIyrcYo.js +0 -1
|
@@ -4,21 +4,22 @@ import { dispatchStreamEventHooks, runHookLifecycleStep } from "#context/hook-li
|
|
|
4
4
|
import { AuthKey, BundleKey, CapabilitiesKey, ChannelKey, ContinuationTokenKey, InitiatorAuthKey, ModeKey, } from "#context/keys.js";
|
|
5
5
|
import { runStep } from "#context/run-step.js";
|
|
6
6
|
import { deserializeContext, serializeContext } from "#context/serialize.js";
|
|
7
|
-
import {
|
|
7
|
+
import { isHarnessBetweenTurns } from "#harness/emission.js";
|
|
8
8
|
import { coalesceTurnInputs } from "#harness/messages.js";
|
|
9
9
|
import { upsertProxyInputRequests } from "#harness/proxy-input-requests.js";
|
|
10
10
|
import { getPendingRuntimeActionBatch, recordPendingSubagentChildToken, } from "#harness/runtime-actions.js";
|
|
11
11
|
import { createLogger, formatError } from "#internal/logging.js";
|
|
12
12
|
import { createSessionFailedEvent, createSubagentCalledEvent, encodeMessageStreamEvent, timestampHandleMessageStreamEvent, } from "#protocol/message.js";
|
|
13
|
-
import { drainPendingConnectionAuthorizations, PendingConnectionAuthorizationsKey, } from "#runtime/framework-tools/connection-search.js";
|
|
14
13
|
import { PendingConnectionToolCallsKey, } from "#runtime/framework-tools/pending-connection-tool-calls.js";
|
|
15
14
|
import { getCompiledRuntimeAgentBundle } from "#runtime/sessions/compiled-agent-cache.js";
|
|
16
15
|
import { createExecutionNodeStep } from "#execution/node-step.js";
|
|
16
|
+
import { resolveRemoteAgentForAction, startRemoteAgentSession, } from "#execution/remote-agent-dispatch.js";
|
|
17
17
|
import { emitProxiedInputRequest, routeDeliverPayload } from "#execution/subagent-hitl-proxy.js";
|
|
18
18
|
import { createSession, refreshSessionFromTurnAgent } from "#execution/session.js";
|
|
19
19
|
import { buildSubagentRunInput } from "#execution/subagent-tool.js";
|
|
20
20
|
import { turnWorkflow } from "#execution/turn-workflow.js";
|
|
21
21
|
import { createWorkflowRuntime, startWorkflowPreferLatest, workflowEntryReference, } from "#execution/workflow-runtime.js";
|
|
22
|
+
import { toErrorMessage } from "#shared/errors.js";
|
|
22
23
|
/**
|
|
23
24
|
* Runs one atomic harness step inside a durable `"use step"` boundary.
|
|
24
25
|
*/
|
|
@@ -100,13 +101,7 @@ export async function turnStep(input) {
|
|
|
100
101
|
mode,
|
|
101
102
|
node: bundle.graph.root,
|
|
102
103
|
});
|
|
103
|
-
|
|
104
|
-
await drainPendingConnectionAuthorizations({
|
|
105
|
-
ctx,
|
|
106
|
-
emit,
|
|
107
|
-
state: getHarnessEmissionState(result.session),
|
|
108
|
-
});
|
|
109
|
-
return result;
|
|
104
|
+
return step(refreshedSession, stepInput);
|
|
110
105
|
};
|
|
111
106
|
// Lifecycle hooks fire only at the start of a new turn — when the
|
|
112
107
|
// workflow has freshly delivered input and the harness is between
|
|
@@ -142,13 +137,11 @@ export async function turnStep(input) {
|
|
|
142
137
|
// serialized context; the resolve step clears them after
|
|
143
138
|
// successful retry.
|
|
144
139
|
const pendingToolCalls = ctx.get(PendingConnectionToolCallsKey) ?? [];
|
|
145
|
-
|
|
146
|
-
if (pendingToolCalls.length > 0 && pendingAuths.length > 0) {
|
|
140
|
+
if (pendingToolCalls.length > 0) {
|
|
147
141
|
writer.releaseLock();
|
|
148
142
|
return {
|
|
149
143
|
action: "await-authorization",
|
|
150
144
|
pendingToolCalls,
|
|
151
|
-
pendingAuths,
|
|
152
145
|
serializedContext: nextSerializedContext,
|
|
153
146
|
session: stepResult.session,
|
|
154
147
|
};
|
|
@@ -210,7 +203,7 @@ export async function dispatchPendingRuntimeActionsStep(input) {
|
|
|
210
203
|
"use step";
|
|
211
204
|
const batch = getPendingRuntimeActionBatch(input.session);
|
|
212
205
|
if (batch === undefined || batch.actions.length === 0) {
|
|
213
|
-
return input.session;
|
|
206
|
+
return { results: [], session: input.session };
|
|
214
207
|
}
|
|
215
208
|
const ctx = await deserializeContext(input.serializedContext);
|
|
216
209
|
const bundle = ctx.require(BundleKey);
|
|
@@ -221,36 +214,73 @@ export async function dispatchPendingRuntimeActionsStep(input) {
|
|
|
221
214
|
const writer = input.parentWritable.getWriter();
|
|
222
215
|
const adapterCtx = buildAdapterContext(adapter, ctx);
|
|
223
216
|
let nextSession = input.session;
|
|
217
|
+
const results = [];
|
|
224
218
|
try {
|
|
225
219
|
for (const action of batch.actions) {
|
|
226
|
-
|
|
227
|
-
|
|
220
|
+
let childSessionId;
|
|
221
|
+
let name;
|
|
222
|
+
let remote;
|
|
223
|
+
let toolName;
|
|
224
|
+
switch (action.kind) {
|
|
225
|
+
case "subagent-call": {
|
|
226
|
+
const childRuntime = createWorkflowRuntime({
|
|
227
|
+
compiledArtifactsSource: bundle.compiledArtifactsSource,
|
|
228
|
+
nodeId: action.nodeId,
|
|
229
|
+
});
|
|
230
|
+
const { childContinuationToken, runInput } = buildSubagentRunInput({
|
|
231
|
+
action,
|
|
232
|
+
auth,
|
|
233
|
+
batchEvent: batch.event,
|
|
234
|
+
capabilities,
|
|
235
|
+
initiatorAuth,
|
|
236
|
+
session: input.session,
|
|
237
|
+
});
|
|
238
|
+
const handle = await childRuntime.run(runInput);
|
|
239
|
+
nextSession = recordPendingSubagentChildToken({
|
|
240
|
+
callId: action.callId,
|
|
241
|
+
childContinuationToken,
|
|
242
|
+
session: nextSession,
|
|
243
|
+
});
|
|
244
|
+
childSessionId = handle.sessionId;
|
|
245
|
+
name = action.name;
|
|
246
|
+
toolName = action.subagentName;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "remote-agent-call": {
|
|
250
|
+
let resolvedRemote;
|
|
251
|
+
try {
|
|
252
|
+
resolvedRemote = resolveRemoteAgentForAction({
|
|
253
|
+
nodeId: action.nodeId,
|
|
254
|
+
remoteAgentName: action.remoteAgentName,
|
|
255
|
+
registry: bundle.subagentRegistry.subagentsByNodeId,
|
|
256
|
+
});
|
|
257
|
+
childSessionId = await startRemoteAgentSession({
|
|
258
|
+
action,
|
|
259
|
+
callbackBaseUrl: input.callbackBaseUrl,
|
|
260
|
+
remote: resolvedRemote,
|
|
261
|
+
session: input.session,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
results.push(createRemoteAgentStartFailureResult({ action, error }));
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
name = action.name;
|
|
269
|
+
remote = { url: resolvedRemote.url };
|
|
270
|
+
toolName = action.remoteAgentName;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
default:
|
|
274
|
+
throw new Error(`Unsupported runtime action kind "${action.kind}" in workflow runtime.`);
|
|
228
275
|
}
|
|
229
|
-
const childRuntime = createWorkflowRuntime({
|
|
230
|
-
compiledArtifactsSource: bundle.compiledArtifactsSource,
|
|
231
|
-
nodeId: action.nodeId,
|
|
232
|
-
});
|
|
233
|
-
const { childContinuationToken, runInput } = buildSubagentRunInput({
|
|
234
|
-
action,
|
|
235
|
-
auth,
|
|
236
|
-
batchEvent: batch.event,
|
|
237
|
-
capabilities,
|
|
238
|
-
initiatorAuth,
|
|
239
|
-
session: input.session,
|
|
240
|
-
});
|
|
241
|
-
const handle = await childRuntime.run(runInput);
|
|
242
|
-
nextSession = recordPendingSubagentChildToken({
|
|
243
|
-
callId: action.callId,
|
|
244
|
-
childContinuationToken,
|
|
245
|
-
session: nextSession,
|
|
246
|
-
});
|
|
247
276
|
const parentEvent = await callAdapterEventHandler(adapter, createSubagentCalledEvent({
|
|
248
277
|
callId: action.callId,
|
|
249
|
-
childSessionId
|
|
250
|
-
name
|
|
278
|
+
childSessionId,
|
|
279
|
+
name,
|
|
280
|
+
remote,
|
|
251
281
|
sequence: batch.event.sequence,
|
|
252
282
|
sessionId: input.session.sessionId,
|
|
253
|
-
toolName
|
|
283
|
+
toolName,
|
|
254
284
|
turnId: batch.event.turnId,
|
|
255
285
|
workflowId: workflowEntryReference.workflowId,
|
|
256
286
|
}), adapterCtx);
|
|
@@ -260,7 +290,19 @@ export async function dispatchPendingRuntimeActionsStep(input) {
|
|
|
260
290
|
finally {
|
|
261
291
|
writer.releaseLock();
|
|
262
292
|
}
|
|
263
|
-
return nextSession;
|
|
293
|
+
return { results, session: nextSession };
|
|
294
|
+
}
|
|
295
|
+
function createRemoteAgentStartFailureResult(input) {
|
|
296
|
+
return {
|
|
297
|
+
callId: input.action.callId,
|
|
298
|
+
isError: true,
|
|
299
|
+
kind: "subagent-result",
|
|
300
|
+
output: {
|
|
301
|
+
code: "REMOTE_AGENT_START_FAILED",
|
|
302
|
+
message: toErrorMessage(input.error),
|
|
303
|
+
},
|
|
304
|
+
subagentName: input.action.remoteAgentName,
|
|
305
|
+
};
|
|
264
306
|
}
|
|
265
307
|
const log = createLogger("execution.workflow-entry");
|
|
266
308
|
/**
|
|
@@ -6,9 +6,10 @@ import type { NeedsApprovalContext } from "#public/definitions/tool.js";
|
|
|
6
6
|
* These tools are surfaced to the model without a local `execute` function.
|
|
7
7
|
* The harness records the tool call and the runtime executes it later.
|
|
8
8
|
*/
|
|
9
|
-
type HarnessRuntimeActionDefinition = {
|
|
10
|
-
readonly kind: "subagent-call";
|
|
9
|
+
export type HarnessRuntimeActionDefinition = {
|
|
10
|
+
readonly kind: "remote-agent-call" | "subagent-call";
|
|
11
11
|
readonly nodeId: string;
|
|
12
|
+
readonly remoteAgentName?: string;
|
|
12
13
|
readonly subagentName: string;
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
@@ -24,4 +25,3 @@ export interface HarnessToolDefinition {
|
|
|
24
25
|
readonly runtimeAction?: HarnessRuntimeActionDefinition;
|
|
25
26
|
readonly toModelOutput?: (output: unknown) => unknown;
|
|
26
27
|
}
|
|
27
|
-
export {};
|
|
@@ -86,6 +86,7 @@ type RuntimeActionAccumulatorItem<TDeliver> = {
|
|
|
86
86
|
export declare function accumulateRuntimeActionResults<TDeliver>(input: {
|
|
87
87
|
readonly bufferedDeliveries: TDeliver[];
|
|
88
88
|
readonly getNext: () => Promise<RuntimeActionAccumulatorItem<TDeliver> | null>;
|
|
89
|
+
readonly initialResults?: readonly RuntimeActionResult[];
|
|
89
90
|
readonly session: HarnessSession;
|
|
90
91
|
}): Promise<RuntimeActionResult[] | null>;
|
|
91
92
|
/**
|
|
@@ -65,7 +65,13 @@ export function recordPendingSubagentChildToken(input) {
|
|
|
65
65
|
*/
|
|
66
66
|
export async function accumulateRuntimeActionResults(input) {
|
|
67
67
|
const batch = getPendingRuntimeActionBatch(input.session);
|
|
68
|
-
const buffered = [];
|
|
68
|
+
const buffered = [...(input.initialResults ?? [])];
|
|
69
|
+
if (batch !== undefined && buffered.length > 0) {
|
|
70
|
+
const ready = resolveRuntimeActionResultsForBatch({ batch, results: buffered });
|
|
71
|
+
if (ready !== undefined) {
|
|
72
|
+
return ready;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
69
75
|
while (true) {
|
|
70
76
|
const item = await input.getNext();
|
|
71
77
|
if (item === null) {
|
|
@@ -249,6 +255,17 @@ export function createRuntimeActionRequestFromToolCall(input) {
|
|
|
249
255
|
subagentName: definition.runtimeAction.subagentName,
|
|
250
256
|
};
|
|
251
257
|
}
|
|
258
|
+
if (definition?.runtimeAction?.kind === "remote-agent-call") {
|
|
259
|
+
return {
|
|
260
|
+
callId: input.toolCall.toolCallId,
|
|
261
|
+
description: definition.description,
|
|
262
|
+
input: resolveToolCallInputObject(input.toolCall.input),
|
|
263
|
+
kind: "remote-agent-call",
|
|
264
|
+
name: definition.name,
|
|
265
|
+
nodeId: definition.runtimeAction.nodeId,
|
|
266
|
+
remoteAgentName: definition.runtimeAction.remoteAgentName ?? definition.name,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
252
269
|
return {
|
|
253
270
|
callId: input.toolCall.toolCallId,
|
|
254
271
|
input: resolveToolCallInputObject(input.toolCall.input),
|
|
@@ -6,7 +6,7 @@ import { ASH_PACKAGE_NAME } from "#internal/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.25.
|
|
9
|
+
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.25.2";
|
|
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",
|
|
@@ -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;
|
|
@@ -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 = "
|
|
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 = "
|
|
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
|
|
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
|
|
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([
|
|
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) {
|