@gea-ai/agent-sdk 0.1.260910-alpha.0 → 0.1.260910-alpha.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/README.md +3 -1253
- package/dist/agent-channel-author-runtime.d.ts +79 -0
- package/dist/agent-channel-author-runtime.d.ts.map +1 -0
- package/dist/agent-channel-author-runtime.js +511 -0
- package/dist/agent-channel-author-runtime.js.map +1 -0
- package/dist/agent-channel-receiver.d.ts +46 -0
- package/dist/agent-channel-receiver.d.ts.map +1 -0
- package/dist/agent-channel-receiver.js +356 -0
- package/dist/agent-channel-receiver.js.map +1 -0
- package/dist/agent-channel-runtime.d.ts +6 -0
- package/dist/agent-channel-runtime.d.ts.map +1 -0
- package/dist/agent-channel-runtime.js +623 -0
- package/dist/agent-channel-runtime.js.map +1 -0
- package/dist/agent-channel-worker.d.ts +22 -0
- package/dist/agent-channel-worker.d.ts.map +1 -0
- package/dist/agent-channel-worker.js +335 -0
- package/dist/agent-channel-worker.js.map +1 -0
- package/dist/agent-session.d.ts.map +1 -1
- package/dist/agent-session.js +177 -4
- package/dist/agent-session.js.map +1 -1
- package/dist/agent-worker.d.ts +27 -1
- package/dist/agent-worker.d.ts.map +1 -1
- package/dist/agent-worker.js +867 -27
- package/dist/agent-worker.js.map +1 -1
- package/dist/channels/feishu-codec.d.ts +18 -0
- package/dist/channels/feishu-codec.d.ts.map +1 -0
- package/dist/channels/feishu-codec.js +109 -0
- package/dist/channels/feishu-codec.js.map +1 -0
- package/dist/channels/feishu-inbound.d.ts +35 -0
- package/dist/channels/feishu-inbound.d.ts.map +1 -0
- package/dist/channels/feishu-inbound.js +136 -0
- package/dist/channels/feishu-inbound.js.map +1 -0
- package/dist/channels/feishu-websocket.d.ts +5 -0
- package/dist/channels/feishu-websocket.d.ts.map +1 -0
- package/dist/channels/feishu-websocket.js +424 -0
- package/dist/channels/feishu-websocket.js.map +1 -0
- package/dist/channels/feishu.d.ts +32 -0
- package/dist/channels/feishu.d.ts.map +1 -0
- package/dist/channels/feishu.js +233 -0
- package/dist/channels/feishu.js.map +1 -0
- package/dist/channels/index.d.ts +180 -0
- package/dist/channels/index.d.ts.map +1 -0
- package/dist/channels/index.js +93 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -8
- package/dist/index.js.map +1 -1
- package/package.json +16 -3
- package/src/channels/README.md +298 -0
- package/src/channels/feishu.ts +295 -0
package/dist/agent-worker.js
CHANGED
|
@@ -8,6 +8,7 @@ import { trace, SpanStatusCode } from "@opentelemetry/api";
|
|
|
8
8
|
import { agentSessionControlIdentitySchema } from "@gea-ai/contract/agent-session";
|
|
9
9
|
import { agentModelCallSchema, } from "@gea-ai/contract/agent-usage";
|
|
10
10
|
import { createAgentContextRuntime } from "./context-runtime.js";
|
|
11
|
+
import { studioExternalUserPrincipalSchema } from "@gea-ai/contract";
|
|
11
12
|
import { replaceInputFiles, withTurnImages } from "./agent-worker-attachments.js";
|
|
12
13
|
import { handleHostedAgentHttp } from "./agent-http.js";
|
|
13
14
|
import { MAX_AGENT_ARTIFACT_IMAGES, MAX_AGENT_ARTIFACT_IMAGE_BYTES, studioArtifactInputFileSchema, studioArtifactCreateUploadSchema, studioArtifactListSchema, } from "@gea-ai/contract/studio-artifacts";
|
|
@@ -22,17 +23,24 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
22
23
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
23
24
|
import { AGENT_MODEL_PROXY_PROTOCOL_VERSION, AGENT_MODEL_PROXY_TRANSPORT, agentCoreModelDescriptorSchema, decodeAgentModelProxyValue, encodeAgentModelProxyValue, } from "@gea-ai/contract/agent-model-proxy";
|
|
24
25
|
import { AGENT_APPLICATION_CHAT_ID_HEADER, AGENT_APPLICATION_MESSAGE_ID_HEADER, AGENT_APPLICATION_RUN_ID_HEADER, agentApplicationKeySchema, agentApplicationRunRequestSchema, } from "@gea-ai/contract";
|
|
25
|
-
import {
|
|
26
|
+
import { AGENT_CHANNEL_EXECUTION_TIMEOUT_MS, agentChannelInvocationContextSchema, agentChannelAddressSchema, agentChannelHttpMessageRequestSchema, agentChannelOpaqueIdSchema, agentChannelProjectTerminalCommandSchema, agentChannelRunTerminalCallbackSchema, agentChannelStartRunCommandSchema, } from "@gea-ai/contract/agent-channels";
|
|
27
|
+
import { agentSessionClaimChannelRunResultSchema, agentSessionCompleteChannelRunResultSchema, agentSessionGetMessageResultSchema, agentSessionGetModelContextResultSchema, agentSessionAttemptIdentitySchema, agentSessionExecutionStartResultSchema, agentSessionExecutionMutationResultSchema, agentSessionPutModelContextResultSchema, } from "@gea-ai/contract/agent-session";
|
|
26
28
|
import { convertToModelMessages, createUIMessageStreamResponse, createUIMessageStream, asSchema, jsonSchema, stepCountIs, streamText, wrapLanguageModel, tool, } from "ai";
|
|
27
29
|
import { agentWorkerConnectionDeleteOutputSchema, agentWorkerConnectionGetOutputSchema, agentWorkerConnectionSetOutputSchema, agentWorkerToolInitializationSchema, MAX_AGENT_WORKER_TOOLS, } from "@gea-ai/contract/agent-worker-tools";
|
|
28
30
|
import { agentToolIdentitySchema, } from "@gea-ai/contract/agent-packages";
|
|
29
31
|
import { z } from "zod";
|
|
32
|
+
import { agentChannelReceiverControlSchema } from "@gea-ai/contract/agent-channel-receivers";
|
|
33
|
+
export { AgentChannelReceiver } from "./agent-channel-receiver.js";
|
|
34
|
+
import { agentChannelRuntimeCommandSchema, agentChannelRuntimeResolveSchema, agentChannelJsonSchema, } from "@gea-ai/contract/agent-channel-runtime";
|
|
35
|
+
import { createChannelRouteContext, executeChannelBehavior, openChannelWebSocket, } from "./agent-channel-author-runtime.js";
|
|
30
36
|
import { createAgentHookRuntimeDefinition, createAgentPackageSnapshot, createAgentToolRuntimeDefinition, } from "./index.js";
|
|
31
37
|
import { addAgentWorkerUsage, assembleAgentWorkerContext, } from "./agent-worker-context.js";
|
|
32
38
|
import { addCacheControlToMessages, addCacheControlToToolList, addCacheControlToTools, } from "./cache-control.js";
|
|
33
39
|
import { agentComputerToolDefaultDescriptions } from "./computer-tools.js";
|
|
34
40
|
import { createAgentSkillTool } from "./skill-tool.js";
|
|
35
41
|
import { agentModelTelemetry, recordExecutionFailure, traceWorkerRequest, } from "./tracing.js";
|
|
42
|
+
import { createAgentChannelStableId, } from "./agent-channel-worker.js";
|
|
43
|
+
export { handleAgentChannelAddressAlarm, handleAgentChannelAddressRequest, } from "./agent-channel-worker.js";
|
|
36
44
|
const HOST_PATH = "/internal/agent-worker-host/v1";
|
|
37
45
|
const modelDescriptorSchema = z.union([
|
|
38
46
|
agentCoreModelDescriptorSchema,
|
|
@@ -104,6 +112,20 @@ async function isPrivateAgentInvocation(environment, session) {
|
|
|
104
112
|
control.agentKey === session.agentKey &&
|
|
105
113
|
control.chatId === session.chatId);
|
|
106
114
|
}
|
|
115
|
+
const agentChannelPlatformIdentitySchema = agentApplicationPlatformIdentitySchema.extend({
|
|
116
|
+
channel: agentChannelInvocationContextSchema,
|
|
117
|
+
});
|
|
118
|
+
const agentChannelReplyMessageSchema = z
|
|
119
|
+
.object({
|
|
120
|
+
id: z.string().trim().min(1).max(256),
|
|
121
|
+
parts: z.array(z
|
|
122
|
+
.object({
|
|
123
|
+
text: z.string().optional(),
|
|
124
|
+
type: z.string().trim().min(1),
|
|
125
|
+
})
|
|
126
|
+
.passthrough()),
|
|
127
|
+
})
|
|
128
|
+
.passthrough();
|
|
107
129
|
function prepareAgentRuntime(entry) {
|
|
108
130
|
const snapshot = createAgentPackageSnapshot(entry.definition);
|
|
109
131
|
const computer = entry.definition.agent.computer;
|
|
@@ -113,6 +135,7 @@ function prepareAgentRuntime(entry) {
|
|
|
113
135
|
? computer.filesystem.scope
|
|
114
136
|
: undefined;
|
|
115
137
|
return {
|
|
138
|
+
channels: entry.definition.channels.filter((channel) => channel.provider === "custom"),
|
|
116
139
|
durableObjectBindingNames: Object.keys(entry.definition.agent.durableObjects ?? {}),
|
|
117
140
|
scopeResolver,
|
|
118
141
|
artifactScopeResolver: entry.definition.agent.artifacts?.scope,
|
|
@@ -196,7 +219,7 @@ async function runPreparedConnectorAuthorization(input, environment) {
|
|
|
196
219
|
});
|
|
197
220
|
return Response.json({ completed: true }, { headers: { "cache-control": "no-store" } });
|
|
198
221
|
}
|
|
199
|
-
async function runPreparedAgent(runtime, agentKey, request, environment) {
|
|
222
|
+
async function runPreparedAgent(runtime, agentKey, request, environment, identifiers) {
|
|
200
223
|
const body = await request.json().catch(() => null);
|
|
201
224
|
const parsed = agentApplicationRunRequestSchema.safeParse(body);
|
|
202
225
|
if (!parsed.success ||
|
|
@@ -210,14 +233,18 @@ async function runPreparedAgent(runtime, agentKey, request, environment) {
|
|
|
210
233
|
(platformIdentity.agentSession.runId !== platformIdentity.run?.id ||
|
|
211
234
|
parsed.data.chatId !== platformIdentity.chat?.id))
|
|
212
235
|
throw new Error("Managed Session invocation identity mismatch.");
|
|
213
|
-
const chatId =
|
|
214
|
-
|
|
215
|
-
|
|
236
|
+
const chatId = identifiers?.chatId ??
|
|
237
|
+
parsed.data.chatId ??
|
|
238
|
+
platformIdentity.chat?.id ??
|
|
239
|
+
crypto.randomUUID();
|
|
240
|
+
const runId = identifiers?.runId ?? platformIdentity.run?.id ?? crypto.randomUUID();
|
|
241
|
+
const responseMessageId = identifiers?.responseMessageId ??
|
|
242
|
+
platformIdentity.responseMessage?.id ??
|
|
216
243
|
(typeof parsed.data.message !== "string" &&
|
|
217
244
|
parsed.data.message.role === "assistant"
|
|
218
245
|
? parsed.data.message.id
|
|
219
246
|
: crypto.randomUUID());
|
|
220
|
-
const userMessageId = crypto.randomUUID();
|
|
247
|
+
const userMessageId = identifiers?.userMessageId ?? crypto.randomUUID();
|
|
221
248
|
const workerIdentity = platformIdentity.worker?.id ?? "worker";
|
|
222
249
|
const identity = agentToolIdentitySchema.parse({
|
|
223
250
|
agent: platformIdentity.agent ?? {
|
|
@@ -238,15 +265,17 @@ async function runPreparedAgent(runtime, agentKey, request, environment) {
|
|
|
238
265
|
get: (id) => environment.AGENT_SESSIONS.get(id),
|
|
239
266
|
idFromName: (name) => environment.AGENT_SESSIONS.idFromName(`${agentKey}:${name}`),
|
|
240
267
|
};
|
|
241
|
-
let message =
|
|
242
|
-
? {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
: parsed.data.message
|
|
268
|
+
let message = identifiers?.message
|
|
269
|
+
? { id: userMessageId, ...identifiers.message }
|
|
270
|
+
: typeof parsed.data.message === "string"
|
|
271
|
+
? {
|
|
272
|
+
id: userMessageId,
|
|
273
|
+
role: "user",
|
|
274
|
+
parts: [{ type: "text", text: parsed.data.message }],
|
|
275
|
+
}
|
|
276
|
+
: parsed.data.message.role === "user"
|
|
277
|
+
? { ...parsed.data.message, id: userMessageId }
|
|
278
|
+
: parsed.data.message;
|
|
250
279
|
if (message.role === "assistant" && !platformIdentity.agentSession) {
|
|
251
280
|
const resumed = await sessionNamespace
|
|
252
281
|
.get(sessionNamespace.idFromName(chatId))
|
|
@@ -273,7 +302,7 @@ async function runPreparedAgent(runtime, agentKey, request, environment) {
|
|
|
273
302
|
cacheControlProvider: null,
|
|
274
303
|
chatId,
|
|
275
304
|
contextArtifactRootPath: `/.gea/chats/${encodeURIComponent(chatId)}/context`,
|
|
276
|
-
currentTurn,
|
|
305
|
+
currentTurn: encodeAgentModelProxyValue(currentTurn),
|
|
277
306
|
metadata: parsed.data.metadata ?? {},
|
|
278
307
|
environment: {},
|
|
279
308
|
instructions: runtime.instructions.trim()
|
|
@@ -318,6 +347,7 @@ async function runPreparedAgent(runtime, agentKey, request, environment) {
|
|
|
318
347
|
context: runtime.context,
|
|
319
348
|
engine: runtime.engine,
|
|
320
349
|
hookRuntime: runtime.hookRuntime,
|
|
350
|
+
channelJournal: identifiers?.journal,
|
|
321
351
|
request: internalRequest,
|
|
322
352
|
sessionNamespace,
|
|
323
353
|
sessionExecution: platformIdentity.agentSession,
|
|
@@ -545,6 +575,798 @@ export function createAgentApplicationFetch(input) {
|
|
|
545
575
|
return new Response("Agent protocol route not found", { status: 404 });
|
|
546
576
|
};
|
|
547
577
|
}
|
|
578
|
+
class AgentChannelTargetMismatch extends Error {
|
|
579
|
+
}
|
|
580
|
+
class AgentChannelInstallationDisabled extends Error {
|
|
581
|
+
}
|
|
582
|
+
function channelError(code, status, message, retryable = false) {
|
|
583
|
+
return Response.json({ error: { code, message, retryable } }, { headers: { "cache-control": "no-store" }, status });
|
|
584
|
+
}
|
|
585
|
+
function scopedAgentSessionNamespace(environment, agentKey) {
|
|
586
|
+
return {
|
|
587
|
+
get: (id) => environment.CHANNEL_SESSIONS.get(id),
|
|
588
|
+
idFromName: (name) => environment.CHANNEL_SESSIONS.idFromName(`${agentKey}:${name}`),
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
async function readAgentChannelIdentity(environment) {
|
|
592
|
+
return agentChannelPlatformIdentitySchema.parse(await requireHostJson(environment.IDENTITY, "/context"));
|
|
593
|
+
}
|
|
594
|
+
async function createChannelAddressInput(input) {
|
|
595
|
+
const context = input.identity.channel;
|
|
596
|
+
const address = {
|
|
597
|
+
agentKey: input.channel.agentKey,
|
|
598
|
+
channelKey: input.channel.channel.key,
|
|
599
|
+
environment: context.environment,
|
|
600
|
+
installationId: context.installationId,
|
|
601
|
+
providerLocalKey: input.message.conversationId,
|
|
602
|
+
principalId: input.message.principal.principalId,
|
|
603
|
+
schemaVersion: "gea.channel-address/v1",
|
|
604
|
+
studioAgentId: context.studioAgentId,
|
|
605
|
+
workspaceId: context.workspaceId,
|
|
606
|
+
};
|
|
607
|
+
const executionTarget = {
|
|
608
|
+
channelDefinitionVersion: await input.channel.definitionVersion,
|
|
609
|
+
schemaVersion: "gea.channel-execution-target/v1",
|
|
610
|
+
sessionId: await createAgentChannelStableId("session", address),
|
|
611
|
+
stateVersion: input.channel.channel.stateVersion,
|
|
612
|
+
studioAgentVersionId: context.studioAgentVersionId,
|
|
613
|
+
workerDeploymentId: context.workerDeploymentId,
|
|
614
|
+
workerId: context.workerId,
|
|
615
|
+
};
|
|
616
|
+
return { address, executionTarget };
|
|
617
|
+
}
|
|
618
|
+
async function forwardChannelAddressRequest(input) {
|
|
619
|
+
const addressId = await createAgentChannelStableId("address", input.address);
|
|
620
|
+
const namespace = input.environment.CHANNEL_ADDRESSES;
|
|
621
|
+
return await namespace
|
|
622
|
+
.get(namespace.idFromName(addressId))
|
|
623
|
+
.fetch(new Request(`http://channel-address.internal${input.path}`, input.init));
|
|
624
|
+
}
|
|
625
|
+
async function assertPinnedChannelCommand(input) {
|
|
626
|
+
const { channel: context } = input.identity;
|
|
627
|
+
const { address, executionTarget } = input.command;
|
|
628
|
+
const expectedAddressId = await createAgentChannelStableId("address", address);
|
|
629
|
+
const expectedSessionId = await createAgentChannelStableId("session", address);
|
|
630
|
+
if (context.mode !== "internal" ||
|
|
631
|
+
context.environment !== address.environment ||
|
|
632
|
+
context.agentKey !== input.channel.agentKey ||
|
|
633
|
+
context.channelKey !== input.channel.channel.key ||
|
|
634
|
+
context.installationId !== address.installationId ||
|
|
635
|
+
context.studioAgentId !== address.studioAgentId ||
|
|
636
|
+
context.workspaceId !== address.workspaceId ||
|
|
637
|
+
context.workerId !== executionTarget.workerId ||
|
|
638
|
+
context.workerDeploymentId !== executionTarget.workerDeploymentId ||
|
|
639
|
+
context.studioAgentVersionId !== executionTarget.studioAgentVersionId ||
|
|
640
|
+
input.command.addressId !== expectedAddressId ||
|
|
641
|
+
executionTarget.sessionId !== expectedSessionId ||
|
|
642
|
+
executionTarget.channelDefinitionVersion !==
|
|
643
|
+
(await input.channel.definitionVersion) ||
|
|
644
|
+
executionTarget.stateVersion !== input.channel.channel.stateVersion) {
|
|
645
|
+
throw new AgentChannelTargetMismatch("Agent Channel execution target does not match.");
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async function sendChannelTerminalCallback(input) {
|
|
649
|
+
const callback = agentChannelRunTerminalCallbackSchema.parse({
|
|
650
|
+
address: input.command.address,
|
|
651
|
+
addressId: input.command.addressId,
|
|
652
|
+
commandId: await createAgentChannelStableId("callback", input.command.addressId, input.command.operationId),
|
|
653
|
+
executionTarget: input.command.executionTarget,
|
|
654
|
+
kind: "run_terminal_callback",
|
|
655
|
+
operationId: input.command.operationId,
|
|
656
|
+
protocolVersion: "gea.channel-command/v1",
|
|
657
|
+
runId: input.command.runId,
|
|
658
|
+
terminal: input.terminal,
|
|
659
|
+
});
|
|
660
|
+
const response = await forwardChannelAddressRequest({
|
|
661
|
+
address: input.command.address,
|
|
662
|
+
environment: input.environment,
|
|
663
|
+
init: {
|
|
664
|
+
body: JSON.stringify(callback),
|
|
665
|
+
headers: { "content-type": "application/json" },
|
|
666
|
+
method: "POST",
|
|
667
|
+
},
|
|
668
|
+
path: "/v1/callback",
|
|
669
|
+
});
|
|
670
|
+
if (!response.ok) {
|
|
671
|
+
throw new Error("ChannelAddress rejected the terminal callback.");
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
async function completeChannelRun(input) {
|
|
675
|
+
return await requestSessionJson({
|
|
676
|
+
body: {
|
|
677
|
+
commandId: input.command.commandId,
|
|
678
|
+
executionEpoch: input.executionEpoch,
|
|
679
|
+
terminal: input.terminal,
|
|
680
|
+
},
|
|
681
|
+
chatId: input.command.executionTarget.sessionId,
|
|
682
|
+
method: "POST",
|
|
683
|
+
namespace: scopedAgentSessionNamespace(input.environment, input.command.address.agentKey),
|
|
684
|
+
path: "/v1/channel-runs/complete",
|
|
685
|
+
schema: agentSessionCompleteChannelRunResultSchema,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function deferChannelTerminalCallback(context, callback) {
|
|
689
|
+
if (context === null ||
|
|
690
|
+
typeof context !== "object" ||
|
|
691
|
+
!("waitUntil" in context) ||
|
|
692
|
+
typeof context.waitUntil !== "function") {
|
|
693
|
+
throw new Error("Agent Channel execution context cannot defer work.");
|
|
694
|
+
}
|
|
695
|
+
context.waitUntil(callback());
|
|
696
|
+
}
|
|
697
|
+
export function extractChannelReply(messageValue, responseMessageId) {
|
|
698
|
+
const message = agentChannelReplyMessageSchema.parse(messageValue);
|
|
699
|
+
if (message.id !== responseMessageId) {
|
|
700
|
+
throw new Error("Agent Channel reply message identity does not match.");
|
|
701
|
+
}
|
|
702
|
+
let text = message.parts
|
|
703
|
+
.filter((part) => part.type === "text" && part.text !== undefined)
|
|
704
|
+
.map((part) => part.text)
|
|
705
|
+
.join("");
|
|
706
|
+
// The terminal reply is the durable delivery payload, even when the model omits a Tool's authorization link.
|
|
707
|
+
for (const part of message.parts) {
|
|
708
|
+
if (!(part.type.startsWith("tool-") || part.type === "dynamic-tool") ||
|
|
709
|
+
part.state !== "output-available")
|
|
710
|
+
continue;
|
|
711
|
+
const authorization = z
|
|
712
|
+
.object({ status: z.literal("authorization_required"), url: z.url() })
|
|
713
|
+
.safeParse(part.output);
|
|
714
|
+
if (authorization.success && !text.includes(authorization.data.url))
|
|
715
|
+
text += `${text ? "\n\n" : ""}${authorization.data.url}`;
|
|
716
|
+
}
|
|
717
|
+
return { messageId: responseMessageId, text };
|
|
718
|
+
}
|
|
719
|
+
async function startPinnedChannelRun(input) {
|
|
720
|
+
await assertPinnedChannelCommand(input);
|
|
721
|
+
if (input.command.input.principal.principalId !==
|
|
722
|
+
input.command.address.principalId ||
|
|
723
|
+
input.command.input.conversationId !==
|
|
724
|
+
input.command.address.providerLocalKey) {
|
|
725
|
+
throw new Error("Agent Channel conversation does not match its address.");
|
|
726
|
+
}
|
|
727
|
+
const context = input.identity.channel;
|
|
728
|
+
const executionDeadline = context.environment === "local" ? undefined : context.executionDeadline;
|
|
729
|
+
if (context.environment !== "local" &&
|
|
730
|
+
(executionDeadline === undefined ||
|
|
731
|
+
executionDeadline <= Date.now() ||
|
|
732
|
+
executionDeadline > Date.now() + AGENT_CHANNEL_EXECUTION_TIMEOUT_MS)) {
|
|
733
|
+
throw new AgentChannelTargetMismatch("A bounded production execution deadline is required.");
|
|
734
|
+
}
|
|
735
|
+
const sessionNamespace = scopedAgentSessionNamespace(input.environment, input.channel.agentKey);
|
|
736
|
+
const claim = await requestSessionJson({
|
|
737
|
+
body: {
|
|
738
|
+
allowExecution: true,
|
|
739
|
+
commandId: input.command.commandId,
|
|
740
|
+
executionEpoch: context.executionEpoch,
|
|
741
|
+
...(executionDeadline === undefined ? {} : { executionDeadline }),
|
|
742
|
+
packageVersionId: input.command.executionTarget.studioAgentVersionId,
|
|
743
|
+
responseMessageId: input.command.responseMessageId,
|
|
744
|
+
runId: input.command.runId,
|
|
745
|
+
userMessageId: input.command.userMessageId,
|
|
746
|
+
workerDeploymentId: input.command.executionTarget.workerDeploymentId,
|
|
747
|
+
},
|
|
748
|
+
chatId: input.command.executionTarget.sessionId,
|
|
749
|
+
method: "POST",
|
|
750
|
+
namespace: sessionNamespace,
|
|
751
|
+
path: "/v1/channel-runs/claim",
|
|
752
|
+
schema: agentSessionClaimChannelRunResultSchema,
|
|
753
|
+
});
|
|
754
|
+
if (claim.action === "disabled")
|
|
755
|
+
throw new AgentChannelInstallationDisabled();
|
|
756
|
+
if (claim.action === "terminal") {
|
|
757
|
+
deferChannelTerminalCallback(input.context, async () => await sendChannelTerminalCallback({
|
|
758
|
+
command: input.command,
|
|
759
|
+
environment: input.environment,
|
|
760
|
+
terminal: claim.command.terminal,
|
|
761
|
+
}));
|
|
762
|
+
return {
|
|
763
|
+
callbackAccepted: true,
|
|
764
|
+
kind: "start_run",
|
|
765
|
+
runId: input.command.runId,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
if (claim.action === "running") {
|
|
769
|
+
throw new Error("Agent Channel command is still running.");
|
|
770
|
+
}
|
|
771
|
+
let terminal;
|
|
772
|
+
const executionAbort = new AbortController();
|
|
773
|
+
const executionTimeout = executionDeadline === undefined
|
|
774
|
+
? undefined
|
|
775
|
+
: setTimeout(() => executionAbort.abort(new Error("Agent Channel execution deadline exceeded.")), Math.max(0, executionDeadline - Date.now()));
|
|
776
|
+
const executionSignal = AbortSignal.any([
|
|
777
|
+
input.request.signal,
|
|
778
|
+
executionAbort.signal,
|
|
779
|
+
]);
|
|
780
|
+
try {
|
|
781
|
+
const runResponse = await runPreparedAgent(input.channel.runtime, input.channel.agentKey, new Request(input.request.url, {
|
|
782
|
+
body: JSON.stringify({
|
|
783
|
+
chatId: input.command.executionTarget.sessionId,
|
|
784
|
+
message: input.command.input.message.text,
|
|
785
|
+
}),
|
|
786
|
+
headers: { "content-type": "application/json" },
|
|
787
|
+
method: "POST",
|
|
788
|
+
signal: executionSignal,
|
|
789
|
+
}), {
|
|
790
|
+
...input.environment,
|
|
791
|
+
AGENT_SESSIONS: input.environment.CHANNEL_SESSIONS,
|
|
792
|
+
}, {
|
|
793
|
+
chatId: input.command.executionTarget.sessionId,
|
|
794
|
+
responseMessageId: input.command.responseMessageId,
|
|
795
|
+
runId: input.command.runId,
|
|
796
|
+
userMessageId: input.command.userMessageId,
|
|
797
|
+
});
|
|
798
|
+
if (!runResponse.ok)
|
|
799
|
+
throw new Error("Agent Run was rejected.");
|
|
800
|
+
await runResponse.arrayBuffer();
|
|
801
|
+
executionSignal.throwIfAborted();
|
|
802
|
+
const storedMessage = await requestSessionJson({
|
|
803
|
+
chatId: input.command.executionTarget.sessionId,
|
|
804
|
+
method: "GET",
|
|
805
|
+
namespace: sessionNamespace,
|
|
806
|
+
path: `/v1/messages/${encodeURIComponent(input.command.responseMessageId)}`,
|
|
807
|
+
schema: agentSessionGetMessageResultSchema,
|
|
808
|
+
});
|
|
809
|
+
terminal = {
|
|
810
|
+
reply: extractChannelReply(storedMessage.message, input.command.responseMessageId),
|
|
811
|
+
status: "succeeded",
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
terminal = {
|
|
816
|
+
error: {
|
|
817
|
+
code: executionAbort.signal.aborted
|
|
818
|
+
? "execution_deadline_exceeded"
|
|
819
|
+
: "run_failed",
|
|
820
|
+
message: executionAbort.signal.aborted
|
|
821
|
+
? "Agent Channel execution exceeded its time limit."
|
|
822
|
+
: "Agent Run failed.",
|
|
823
|
+
retryable: false,
|
|
824
|
+
},
|
|
825
|
+
status: "failed",
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
finally {
|
|
829
|
+
if (executionTimeout !== undefined)
|
|
830
|
+
clearTimeout(executionTimeout);
|
|
831
|
+
}
|
|
832
|
+
await completeChannelRun({
|
|
833
|
+
command: input.command,
|
|
834
|
+
environment: input.environment,
|
|
835
|
+
executionEpoch: context.executionEpoch,
|
|
836
|
+
terminal,
|
|
837
|
+
});
|
|
838
|
+
deferChannelTerminalCallback(input.context, async () => await sendChannelTerminalCallback({
|
|
839
|
+
command: input.command,
|
|
840
|
+
environment: input.environment,
|
|
841
|
+
terminal,
|
|
842
|
+
}));
|
|
843
|
+
return {
|
|
844
|
+
callbackAccepted: true,
|
|
845
|
+
kind: "start_run",
|
|
846
|
+
runId: input.command.runId,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
async function startCustomChannelRun(input) {
|
|
850
|
+
await assertPinnedChannelCommand(input);
|
|
851
|
+
const { command, environment } = input;
|
|
852
|
+
if (command.input.principal.principalId !== command.address.principalId ||
|
|
853
|
+
command.executionEpoch !== input.identity.channel.executionEpoch ||
|
|
854
|
+
command.executionDeadline !== input.identity.channel.executionDeadline ||
|
|
855
|
+
command.executionDeadline <= Date.now() ||
|
|
856
|
+
command.executionDeadline > Date.now() + AGENT_CHANNEL_EXECUTION_TIMEOUT_MS)
|
|
857
|
+
throw new AgentChannelTargetMismatch("Channel command execution lease does not match its invocation.");
|
|
858
|
+
const namespace = scopedAgentSessionNamespace(environment, command.address.agentKey);
|
|
859
|
+
const claim = await requestSessionJson({
|
|
860
|
+
namespace,
|
|
861
|
+
chatId: command.executionTarget.sessionId,
|
|
862
|
+
path: "/v1/channel-runs/claim",
|
|
863
|
+
method: "POST",
|
|
864
|
+
schema: agentSessionClaimChannelRunResultSchema,
|
|
865
|
+
body: {
|
|
866
|
+
allowExecution: true,
|
|
867
|
+
commandId: command.commandId,
|
|
868
|
+
executionEpoch: command.executionEpoch,
|
|
869
|
+
executionDeadline: command.executionDeadline,
|
|
870
|
+
packageVersionId: command.executionTarget.studioAgentVersionId,
|
|
871
|
+
responseMessageId: command.responseMessageId,
|
|
872
|
+
runId: command.runId,
|
|
873
|
+
userMessageId: command.userMessageId,
|
|
874
|
+
workerDeploymentId: command.executionTarget.workerDeploymentId,
|
|
875
|
+
},
|
|
876
|
+
});
|
|
877
|
+
if (claim.action === "disabled")
|
|
878
|
+
throw new AgentChannelInstallationDisabled();
|
|
879
|
+
const receipt = {
|
|
880
|
+
accepted: true,
|
|
881
|
+
commandId: command.commandId,
|
|
882
|
+
executionEpoch: command.executionEpoch,
|
|
883
|
+
runId: command.runId,
|
|
884
|
+
};
|
|
885
|
+
const encoder = new TextEncoder();
|
|
886
|
+
const encode = (value) => encoder.encode(`${JSON.stringify(value)}\n`);
|
|
887
|
+
if (claim.action !== "execute")
|
|
888
|
+
return new Response(encode(receipt), {
|
|
889
|
+
headers: { "content-type": "application/x-ndjson" },
|
|
890
|
+
});
|
|
891
|
+
const journal = {
|
|
892
|
+
commandId: command.commandId,
|
|
893
|
+
executionEpoch: command.executionEpoch,
|
|
894
|
+
operationId: command.operationId,
|
|
895
|
+
runId: command.runId,
|
|
896
|
+
attempt: claim.command.attempt,
|
|
897
|
+
};
|
|
898
|
+
await appendSessionValue({
|
|
899
|
+
namespace,
|
|
900
|
+
chatId: command.executionTarget.sessionId,
|
|
901
|
+
path: "/v1/messages",
|
|
902
|
+
body: { message: { id: command.userMessageId, ...command.input.message } },
|
|
903
|
+
});
|
|
904
|
+
await appendSessionValue({
|
|
905
|
+
namespace,
|
|
906
|
+
chatId: command.executionTarget.sessionId,
|
|
907
|
+
path: "/v2/events",
|
|
908
|
+
body: {
|
|
909
|
+
...journal,
|
|
910
|
+
id: `${command.runId}:${journal.attempt}:started`,
|
|
911
|
+
type: "run.started",
|
|
912
|
+
data: {},
|
|
913
|
+
},
|
|
914
|
+
});
|
|
915
|
+
const abort = new AbortController();
|
|
916
|
+
const timer = setTimeout(() => abort.abort(new Error("Channel execution deadline exceeded.")), Math.max(0, command.executionDeadline - Date.now()));
|
|
917
|
+
const signal = AbortSignal.any([abort.signal, input.request.signal]);
|
|
918
|
+
const body = new ReadableStream({
|
|
919
|
+
start(controller) {
|
|
920
|
+
controller.enqueue(encode(receipt));
|
|
921
|
+
// The open response owns this invocation through completion. The Host drains it
|
|
922
|
+
// independently after returning the first, durably accepted receipt to AddressDO.
|
|
923
|
+
const execute = async () => {
|
|
924
|
+
let terminal;
|
|
925
|
+
try {
|
|
926
|
+
const response = await runPreparedAgent(input.channel.runtime, input.channel.agentKey, new Request(input.request.url, {
|
|
927
|
+
method: "POST",
|
|
928
|
+
headers: { "content-type": "application/json" },
|
|
929
|
+
body: JSON.stringify({
|
|
930
|
+
chatId: command.executionTarget.sessionId,
|
|
931
|
+
message: "Channel input",
|
|
932
|
+
}),
|
|
933
|
+
signal,
|
|
934
|
+
}), { ...environment, AGENT_SESSIONS: environment.CHANNEL_SESSIONS }, {
|
|
935
|
+
chatId: command.executionTarget.sessionId,
|
|
936
|
+
responseMessageId: command.responseMessageId,
|
|
937
|
+
runId: command.runId,
|
|
938
|
+
userMessageId: command.userMessageId,
|
|
939
|
+
message: command.input.message,
|
|
940
|
+
journal,
|
|
941
|
+
});
|
|
942
|
+
if (!response.ok)
|
|
943
|
+
throw new Error("Agent execution was rejected.");
|
|
944
|
+
// Drain, rather than buffer, the model's potentially long stream.
|
|
945
|
+
const reader = response.body?.getReader();
|
|
946
|
+
if (reader)
|
|
947
|
+
try {
|
|
948
|
+
while (!(await reader.read()).done)
|
|
949
|
+
signal.throwIfAborted();
|
|
950
|
+
}
|
|
951
|
+
finally {
|
|
952
|
+
reader.releaseLock();
|
|
953
|
+
}
|
|
954
|
+
signal.throwIfAborted();
|
|
955
|
+
const stored = await requestSessionJson({
|
|
956
|
+
namespace,
|
|
957
|
+
chatId: command.executionTarget.sessionId,
|
|
958
|
+
path: `/v1/messages/${encodeURIComponent(command.responseMessageId)}`,
|
|
959
|
+
method: "GET",
|
|
960
|
+
schema: agentSessionGetMessageResultSchema,
|
|
961
|
+
});
|
|
962
|
+
terminal = {
|
|
963
|
+
status: "succeeded",
|
|
964
|
+
reply: extractChannelReply(stored.message, command.responseMessageId),
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
catch {
|
|
968
|
+
terminal = {
|
|
969
|
+
status: "failed",
|
|
970
|
+
error: {
|
|
971
|
+
code: signal.aborted
|
|
972
|
+
? "execution_deadline_exceeded"
|
|
973
|
+
: "run_failed",
|
|
974
|
+
message: signal.aborted
|
|
975
|
+
? "Channel execution exceeded its deadline."
|
|
976
|
+
: "Agent execution failed.",
|
|
977
|
+
retryable: false,
|
|
978
|
+
},
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
await requestSessionJson({
|
|
982
|
+
namespace,
|
|
983
|
+
chatId: command.executionTarget.sessionId,
|
|
984
|
+
path: "/v2/channel-runs/complete",
|
|
985
|
+
method: "POST",
|
|
986
|
+
schema: agentSessionCompleteChannelRunResultSchema,
|
|
987
|
+
body: {
|
|
988
|
+
commandId: command.commandId,
|
|
989
|
+
executionEpoch: command.executionEpoch,
|
|
990
|
+
terminal,
|
|
991
|
+
event: {
|
|
992
|
+
...journal,
|
|
993
|
+
id: `${command.runId}:${journal.attempt}:terminal`,
|
|
994
|
+
type: terminal.status === "succeeded"
|
|
995
|
+
? "run.succeeded"
|
|
996
|
+
: "run.failed",
|
|
997
|
+
data: terminal.status === "succeeded"
|
|
998
|
+
? { reply: terminal.reply }
|
|
999
|
+
: { error: terminal.error },
|
|
1000
|
+
},
|
|
1001
|
+
},
|
|
1002
|
+
});
|
|
1003
|
+
clearTimeout(timer);
|
|
1004
|
+
await forwardChannelAddressRequest({
|
|
1005
|
+
address: command.address,
|
|
1006
|
+
environment,
|
|
1007
|
+
path: "/v2/wake",
|
|
1008
|
+
init: { method: "POST" },
|
|
1009
|
+
});
|
|
1010
|
+
controller.enqueue(encode({ terminal }));
|
|
1011
|
+
controller.close();
|
|
1012
|
+
};
|
|
1013
|
+
void execute().catch((error) => {
|
|
1014
|
+
clearTimeout(timer);
|
|
1015
|
+
controller.error(error);
|
|
1016
|
+
});
|
|
1017
|
+
},
|
|
1018
|
+
cancel() {
|
|
1019
|
+
abort.abort(new Error("Execution host disconnected."));
|
|
1020
|
+
clearTimeout(timer);
|
|
1021
|
+
},
|
|
1022
|
+
});
|
|
1023
|
+
return new Response(body, {
|
|
1024
|
+
headers: {
|
|
1025
|
+
"content-type": "application/x-ndjson",
|
|
1026
|
+
"cache-control": "no-store",
|
|
1027
|
+
},
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Creates file-authored Channels and the exact-executionTarget internal protocol.
|
|
1032
|
+
* Channels belong to each Main Agent's composed package.
|
|
1033
|
+
*/
|
|
1034
|
+
export function createAgentChannelApplicationFetch(input) {
|
|
1035
|
+
const channels = Object.entries(input.agents)
|
|
1036
|
+
.flatMap(([agentKey, definition]) => {
|
|
1037
|
+
const runtime = prepareAgentRuntime(definition);
|
|
1038
|
+
return runtime.snapshot.channels.map((channel) => ({
|
|
1039
|
+
agentKey,
|
|
1040
|
+
channel,
|
|
1041
|
+
definitionVersion: createAgentChannelStableId("definition", channel),
|
|
1042
|
+
runtime,
|
|
1043
|
+
}));
|
|
1044
|
+
})
|
|
1045
|
+
.sort((left, right) => left.channel.path.localeCompare(right.channel.path));
|
|
1046
|
+
return async (request, environment, context) => {
|
|
1047
|
+
const url = new URL(request.url);
|
|
1048
|
+
if (url.pathname === "/_gea/channels/v3/receiver") {
|
|
1049
|
+
if (request.method !== "POST")
|
|
1050
|
+
return channelError("invalid_request", 405, "POST is required.");
|
|
1051
|
+
try {
|
|
1052
|
+
const control = agentChannelReceiverControlSchema.parse(await request.json());
|
|
1053
|
+
const identity = await readAgentChannelIdentity(environment);
|
|
1054
|
+
if (identity.channel.mode !== "internal" ||
|
|
1055
|
+
identity.channel.installationId !== control.installationId ||
|
|
1056
|
+
identity.channel.agentKey !== control.agentKey ||
|
|
1057
|
+
identity.channel.channelKey !== control.channelKey)
|
|
1058
|
+
return channelError("forbidden", 403, "Receiver control identity does not match.");
|
|
1059
|
+
if (!environment.CHANNEL_RECEIVERS)
|
|
1060
|
+
return channelError("unsupported_capability", 503, "Managed receivers are unavailable.");
|
|
1061
|
+
const name = await createAgentChannelStableId("receiver", control.installationId);
|
|
1062
|
+
return await environment.CHANNEL_RECEIVERS.get(environment.CHANNEL_RECEIVERS.idFromName(name)).fetch(new Request("http://receiver/control", {
|
|
1063
|
+
method: "POST",
|
|
1064
|
+
headers: { "content-type": "application/json" },
|
|
1065
|
+
body: JSON.stringify(control),
|
|
1066
|
+
}));
|
|
1067
|
+
}
|
|
1068
|
+
catch {
|
|
1069
|
+
return channelError("invalid_receiver_control", 403, "Receiver control was rejected.");
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
if (url.pathname.startsWith("/_gea/channels/v2/")) {
|
|
1073
|
+
if (request.method !== "POST")
|
|
1074
|
+
return channelError("invalid_request", 405, "POST is required.");
|
|
1075
|
+
try {
|
|
1076
|
+
const identity = await readAgentChannelIdentity(environment);
|
|
1077
|
+
if (url.pathname === "/_gea/channels/v2/address") {
|
|
1078
|
+
const body = z
|
|
1079
|
+
.object({
|
|
1080
|
+
address: agentChannelAddressSchema,
|
|
1081
|
+
path: z.string(),
|
|
1082
|
+
body: z.unknown().optional(),
|
|
1083
|
+
})
|
|
1084
|
+
.strict()
|
|
1085
|
+
.parse(await request.json());
|
|
1086
|
+
const trusted = identity.channel;
|
|
1087
|
+
if (trusted.mode !== "internal" ||
|
|
1088
|
+
trusted.installationId !== body.address.installationId ||
|
|
1089
|
+
trusted.agentKey !== body.address.agentKey ||
|
|
1090
|
+
trusted.channelKey !== body.address.channelKey ||
|
|
1091
|
+
trusted.workspaceId !== body.address.workspaceId ||
|
|
1092
|
+
trusted.studioAgentId !== body.address.studioAgentId ||
|
|
1093
|
+
trusted.environment !== body.address.environment)
|
|
1094
|
+
throw new AgentChannelTargetMismatch();
|
|
1095
|
+
const addressUrl = new URL(body.path, "http://channel-address.internal");
|
|
1096
|
+
if (addressUrl.origin !== "http://channel-address.internal" ||
|
|
1097
|
+
addressUrl.hash ||
|
|
1098
|
+
(addressUrl.pathname !== "/v2/deliveries" &&
|
|
1099
|
+
addressUrl.pathname !== "/v2/deliveries/resolve" &&
|
|
1100
|
+
!/^\/v2\/(operations|status)\/[A-Za-z0-9._~-]+$/u.test(addressUrl.pathname)))
|
|
1101
|
+
return channelError("invalid_request", 400, "Unsupported address operation.");
|
|
1102
|
+
const operation = addressUrl.pathname === "/v2/deliveries/resolve"
|
|
1103
|
+
? agentChannelRuntimeResolveSchema.parse(body.body)
|
|
1104
|
+
: undefined;
|
|
1105
|
+
return await forwardChannelAddressRequest({
|
|
1106
|
+
address: body.address,
|
|
1107
|
+
environment,
|
|
1108
|
+
path: `${addressUrl.pathname}${addressUrl.search}`,
|
|
1109
|
+
...(operation
|
|
1110
|
+
? {
|
|
1111
|
+
init: {
|
|
1112
|
+
method: "POST",
|
|
1113
|
+
headers: { "content-type": "application/json" },
|
|
1114
|
+
body: JSON.stringify(operation),
|
|
1115
|
+
},
|
|
1116
|
+
}
|
|
1117
|
+
: {}),
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
const command = agentChannelRuntimeCommandSchema.parse(await request.json());
|
|
1121
|
+
if (url.pathname !== `/_gea/channels/v2/${command.kind}`)
|
|
1122
|
+
return channelError("invalid_request", 400, "Channel command path mismatch.");
|
|
1123
|
+
const channel = channels.find((entry) => entry.agentKey === command.address.agentKey &&
|
|
1124
|
+
entry.channel.key === command.address.channelKey &&
|
|
1125
|
+
entry.channel.provider === "custom");
|
|
1126
|
+
if (!channel)
|
|
1127
|
+
return channelError("invalid_request", 404, "Custom Channel not found.");
|
|
1128
|
+
await assertPinnedChannelCommand({ channel, command, identity });
|
|
1129
|
+
if (command.kind === "start_run")
|
|
1130
|
+
return await startCustomChannelRun({
|
|
1131
|
+
channel,
|
|
1132
|
+
command,
|
|
1133
|
+
identity,
|
|
1134
|
+
environment,
|
|
1135
|
+
request,
|
|
1136
|
+
});
|
|
1137
|
+
const definition = channel.runtime.channels.find((entry) => entry.key === channel.channel.key);
|
|
1138
|
+
return Response.json(await executeChannelBehavior({
|
|
1139
|
+
definition,
|
|
1140
|
+
command,
|
|
1141
|
+
environment,
|
|
1142
|
+
signal: request.signal,
|
|
1143
|
+
}));
|
|
1144
|
+
}
|
|
1145
|
+
catch (error) {
|
|
1146
|
+
if (error instanceof z.ZodError || error instanceof SyntaxError)
|
|
1147
|
+
return channelError("invalid_request", 400, "Invalid Channel command.");
|
|
1148
|
+
if (error instanceof AgentChannelTargetMismatch)
|
|
1149
|
+
return channelError("channel_execution_target_mismatch", 403, "Channel execution target does not match.");
|
|
1150
|
+
if (error instanceof AgentChannelInstallationDisabled)
|
|
1151
|
+
return channelError("installation_disabled", 403, "Channel installation is disabled.");
|
|
1152
|
+
return channelError("channel_command_failed", 503, "Channel command execution failed.", true);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
const internalPath = url.pathname.startsWith("/_gea/channels/v1/")
|
|
1156
|
+
? url.pathname.slice("/_gea/channels/v1/".length)
|
|
1157
|
+
: null;
|
|
1158
|
+
if (internalPath !== null) {
|
|
1159
|
+
if (request.method !== "POST") {
|
|
1160
|
+
return new Response("Method Not Allowed", {
|
|
1161
|
+
headers: { allow: "POST" },
|
|
1162
|
+
status: 405,
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
try {
|
|
1166
|
+
const identity = await readAgentChannelIdentity(environment);
|
|
1167
|
+
const body = await request.json();
|
|
1168
|
+
if (internalPath === "start") {
|
|
1169
|
+
const command = agentChannelStartRunCommandSchema.parse(body);
|
|
1170
|
+
const channel = channels.find((entry) => entry.agentKey === command.address.agentKey &&
|
|
1171
|
+
entry.channel.key === command.address.channelKey);
|
|
1172
|
+
if (!channel)
|
|
1173
|
+
return channelError("invalid_request", 404, "Channel not found.");
|
|
1174
|
+
return Response.json(await startPinnedChannelRun({
|
|
1175
|
+
channel,
|
|
1176
|
+
command,
|
|
1177
|
+
context,
|
|
1178
|
+
environment,
|
|
1179
|
+
identity,
|
|
1180
|
+
request,
|
|
1181
|
+
}));
|
|
1182
|
+
}
|
|
1183
|
+
if (internalPath === "project") {
|
|
1184
|
+
const command = agentChannelProjectTerminalCommandSchema.parse(body);
|
|
1185
|
+
const channel = channels.find((entry) => entry.agentKey === command.address.agentKey &&
|
|
1186
|
+
entry.channel.key === command.address.channelKey);
|
|
1187
|
+
if (!channel)
|
|
1188
|
+
return channelError("invalid_request", 404, "Channel not found.");
|
|
1189
|
+
await assertPinnedChannelCommand({ channel, command, identity });
|
|
1190
|
+
return Response.json({
|
|
1191
|
+
kind: "project_terminal",
|
|
1192
|
+
runId: command.runId,
|
|
1193
|
+
terminal: command.terminal,
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
if (internalPath === "callback") {
|
|
1197
|
+
const command = agentChannelRunTerminalCallbackSchema.parse(body);
|
|
1198
|
+
const channel = channels.find((entry) => entry.agentKey === command.address.agentKey &&
|
|
1199
|
+
entry.channel.key === command.address.channelKey);
|
|
1200
|
+
if (!channel)
|
|
1201
|
+
return channelError("invalid_request", 404, "Channel not found.");
|
|
1202
|
+
await assertPinnedChannelCommand({ channel, command, identity });
|
|
1203
|
+
const response = await forwardChannelAddressRequest({
|
|
1204
|
+
address: command.address,
|
|
1205
|
+
environment,
|
|
1206
|
+
init: {
|
|
1207
|
+
body: JSON.stringify(command),
|
|
1208
|
+
headers: { "content-type": "application/json" },
|
|
1209
|
+
method: "POST",
|
|
1210
|
+
},
|
|
1211
|
+
path: "/v1/callback",
|
|
1212
|
+
});
|
|
1213
|
+
return new Response(response.body, response);
|
|
1214
|
+
}
|
|
1215
|
+
return channelError("invalid_request", 404, "Internal Channel route not found.");
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
if (error instanceof AgentChannelInstallationDisabled) {
|
|
1219
|
+
return channelError("installation_disabled", 403, "The Channel installation is disabled.");
|
|
1220
|
+
}
|
|
1221
|
+
if (error instanceof z.ZodError || error instanceof SyntaxError) {
|
|
1222
|
+
return channelError("invalid_request", 400, "Invalid Channel request.");
|
|
1223
|
+
}
|
|
1224
|
+
return error instanceof AgentChannelTargetMismatch
|
|
1225
|
+
? channelError("channel_execution_target_mismatch", 403, "Channel execution target does not match.")
|
|
1226
|
+
: channelError("run_failed", 503, "Channel command execution failed and will be retried.", true);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const channel = channels.find((entry) => url.pathname === entry.channel.path ||
|
|
1230
|
+
url.pathname.startsWith(`${entry.channel.path}/`));
|
|
1231
|
+
if (!channel)
|
|
1232
|
+
return undefined;
|
|
1233
|
+
if (channel.channel.provider === "http" &&
|
|
1234
|
+
!["GET", "HEAD", "OPTIONS", "POST"].includes(request.method)) {
|
|
1235
|
+
return undefined;
|
|
1236
|
+
}
|
|
1237
|
+
let identity;
|
|
1238
|
+
try {
|
|
1239
|
+
identity = await readAgentChannelIdentity(environment);
|
|
1240
|
+
}
|
|
1241
|
+
catch {
|
|
1242
|
+
return channelError("unauthorized_installation", 401, "A valid Channel installation is required.");
|
|
1243
|
+
}
|
|
1244
|
+
if (identity.channel.mode !== "ingress" ||
|
|
1245
|
+
identity.channel.agentKey !== channel.agentKey ||
|
|
1246
|
+
identity.channel.channelKey !== channel.channel.key) {
|
|
1247
|
+
return channelError("unauthorized_installation", 403, "The Channel installation does not own this route.");
|
|
1248
|
+
}
|
|
1249
|
+
const relativePath = url.pathname.slice(channel.channel.path.length);
|
|
1250
|
+
if (channel.channel.provider === "custom") {
|
|
1251
|
+
if (identity.channel.installationStatus === "retired" ||
|
|
1252
|
+
(identity.channel.installationStatus === "disabled" &&
|
|
1253
|
+
request.headers.get("upgrade")?.toLowerCase() === "websocket"))
|
|
1254
|
+
return channelError("installation_disabled", 403, "Channel installation is unavailable.");
|
|
1255
|
+
const definition = channel.runtime.channels.find((entry) => entry.key === channel.channel.key);
|
|
1256
|
+
const isSocket = request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
1257
|
+
const route = definition.routes.find((entry) => entry.path === (relativePath || "/") &&
|
|
1258
|
+
entry.method === (isSocket ? "WS" : request.method));
|
|
1259
|
+
if (!route)
|
|
1260
|
+
return channelError("route_not_found", 404, "Channel route not found.");
|
|
1261
|
+
const abort = new AbortController();
|
|
1262
|
+
try {
|
|
1263
|
+
const authorContext = await createChannelRouteContext({
|
|
1264
|
+
definition,
|
|
1265
|
+
environment,
|
|
1266
|
+
identity: identity.channel,
|
|
1267
|
+
signal: isSocket ? abort.signal : request.signal,
|
|
1268
|
+
request,
|
|
1269
|
+
});
|
|
1270
|
+
return route.method === "WS"
|
|
1271
|
+
? await openChannelWebSocket(route, request, authorContext, abort, environment)
|
|
1272
|
+
: await route.handler(request, authorContext);
|
|
1273
|
+
}
|
|
1274
|
+
catch (error) {
|
|
1275
|
+
abort.abort();
|
|
1276
|
+
console.error("Channel route callback failed", {
|
|
1277
|
+
channelKey: channel.channel.key,
|
|
1278
|
+
error: error instanceof Error &&
|
|
1279
|
+
/^(Channel capability|Channel address|Channel session)/u.test(error.message)
|
|
1280
|
+
? error.message
|
|
1281
|
+
: error instanceof Error
|
|
1282
|
+
? error.name
|
|
1283
|
+
: "unknown",
|
|
1284
|
+
});
|
|
1285
|
+
return channelError("channel_handler_failed", error instanceof z.ZodError ? 400 : 503, "Channel handler failed.");
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (relativePath === "/v1/messages") {
|
|
1289
|
+
if (request.method !== "POST") {
|
|
1290
|
+
return new Response("Method Not Allowed", {
|
|
1291
|
+
headers: { allow: "POST" },
|
|
1292
|
+
status: 405,
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
if (identity.channel.installationStatus === "disabled") {
|
|
1296
|
+
return channelError("installation_disabled", 403, "The Channel installation is disabled.");
|
|
1297
|
+
}
|
|
1298
|
+
if (identity.channel.installationStatus === "retired") {
|
|
1299
|
+
return channelError("installation_retired", 410, "The Channel installation is retired.");
|
|
1300
|
+
}
|
|
1301
|
+
try {
|
|
1302
|
+
const message = agentChannelHttpMessageRequestSchema.parse(await request.json());
|
|
1303
|
+
const target = await createChannelAddressInput({
|
|
1304
|
+
channel,
|
|
1305
|
+
identity,
|
|
1306
|
+
message,
|
|
1307
|
+
});
|
|
1308
|
+
const response = await forwardChannelAddressRequest({
|
|
1309
|
+
address: target.address,
|
|
1310
|
+
environment,
|
|
1311
|
+
init: {
|
|
1312
|
+
body: JSON.stringify({
|
|
1313
|
+
...target,
|
|
1314
|
+
channelPath: channel.channel.path,
|
|
1315
|
+
input: message,
|
|
1316
|
+
}),
|
|
1317
|
+
headers: { "content-type": "application/json" },
|
|
1318
|
+
method: "POST",
|
|
1319
|
+
},
|
|
1320
|
+
path: "/v1/ingress",
|
|
1321
|
+
});
|
|
1322
|
+
return new Response(response.body, response);
|
|
1323
|
+
}
|
|
1324
|
+
catch (error) {
|
|
1325
|
+
if (error instanceof z.ZodError || error instanceof SyntaxError) {
|
|
1326
|
+
return channelError("invalid_request", 400, "Invalid Channel request.");
|
|
1327
|
+
}
|
|
1328
|
+
throw error;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
const statusSegments = relativePath.split("/").filter(Boolean);
|
|
1332
|
+
if (request.method === "GET" &&
|
|
1333
|
+
statusSegments.length === 5 &&
|
|
1334
|
+
statusSegments[0] === "v1" &&
|
|
1335
|
+
statusSegments[1] === "conversations" &&
|
|
1336
|
+
statusSegments[3] === "operations") {
|
|
1337
|
+
try {
|
|
1338
|
+
const conversationId = agentChannelOpaqueIdSchema.parse(decodeURIComponent(statusSegments[2]));
|
|
1339
|
+
const operationId = agentChannelOpaqueIdSchema.parse(decodeURIComponent(statusSegments[4]));
|
|
1340
|
+
const target = await createChannelAddressInput({
|
|
1341
|
+
channel,
|
|
1342
|
+
identity,
|
|
1343
|
+
message: {
|
|
1344
|
+
conversationId,
|
|
1345
|
+
principal: studioExternalUserPrincipalSchema.parse({
|
|
1346
|
+
principalType: "user",
|
|
1347
|
+
principalId: new URL(request.url).searchParams.get("principalId"),
|
|
1348
|
+
}),
|
|
1349
|
+
message: { text: "status", type: "text" },
|
|
1350
|
+
operationId,
|
|
1351
|
+
},
|
|
1352
|
+
});
|
|
1353
|
+
const response = await forwardChannelAddressRequest({
|
|
1354
|
+
address: target.address,
|
|
1355
|
+
environment,
|
|
1356
|
+
path: `/v1/operations/${encodeURIComponent(operationId)}`,
|
|
1357
|
+
});
|
|
1358
|
+
return new Response(response.body, response);
|
|
1359
|
+
}
|
|
1360
|
+
catch (error) {
|
|
1361
|
+
if (error instanceof z.ZodError || error instanceof URIError) {
|
|
1362
|
+
return channelError("invalid_request", 400, "Invalid Channel request.");
|
|
1363
|
+
}
|
|
1364
|
+
throw error;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return channelError("not_found", 404, "Channel route not found.");
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
548
1370
|
function createAgentWorkerHostAdapter(bindings) {
|
|
549
1371
|
return {
|
|
550
1372
|
async fetch(path, init) {
|
|
@@ -1943,8 +2765,6 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer) {
|
|
|
1943
2765
|
}
|
|
1944
2766
|
const payload = runRequestSchema.parse(await input.request.json());
|
|
1945
2767
|
const sessionExecution = input.sessionExecution;
|
|
1946
|
-
if (input.snapshot.engine !== input.engine?.kind)
|
|
1947
|
-
throw new Error("Agent engine binding does not match its immutable snapshot.");
|
|
1948
2768
|
let mutationSequence = 1;
|
|
1949
2769
|
let writeTail = Promise.resolve();
|
|
1950
2770
|
let writeFailure;
|
|
@@ -2384,8 +3204,9 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer) {
|
|
|
2384
3204
|
? undefined
|
|
2385
3205
|
: await createGeaAI(host, configuredModel, payload.requestOptions?.extraBody, { kind: "agent-dev", packageName: input.snapshot.name });
|
|
2386
3206
|
const modelMessages = replaceInputFiles(await convertToModelMessages(payload.originalMessages), inputFiles);
|
|
2387
|
-
|
|
2388
|
-
|
|
3207
|
+
// Model file parts can contain URL and byte values that ordinary JSON cannot preserve.
|
|
3208
|
+
const runtimeContext = decodeAgentModelProxyValue(payload.runtimeContext);
|
|
3209
|
+
let currentTurn = decodeAgentModelProxyValue(payload.currentTurn);
|
|
2389
3210
|
const storedContext = await requestSessionJson({
|
|
2390
3211
|
chatId: payload.chatId,
|
|
2391
3212
|
method: "GET",
|
|
@@ -2411,15 +3232,14 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer) {
|
|
|
2411
3232
|
contextSize: storedContext.context.contextSize,
|
|
2412
3233
|
messages: (input.engine && storedContext.context.metadata.agentCore) ||
|
|
2413
3234
|
storedContext.context.runId === payload.runId
|
|
2414
|
-
? storedContext.context.messages
|
|
3235
|
+
? storedContext.context.messages.map(decodeAgentModelProxyValue)
|
|
2415
3236
|
: currentTurn.length > 0
|
|
2416
3237
|
? [
|
|
2417
|
-
...storedContext.context.messages,
|
|
3238
|
+
...storedContext.context.messages.map(decodeAgentModelProxyValue),
|
|
2418
3239
|
...currentTurn,
|
|
2419
3240
|
]
|
|
2420
3241
|
: replaceLastAssistantInteraction({
|
|
2421
|
-
managedMessages: storedContext.context
|
|
2422
|
-
.messages,
|
|
3242
|
+
managedMessages: storedContext.context.messages.map(decodeAgentModelProxyValue),
|
|
2423
3243
|
modelMessages,
|
|
2424
3244
|
}),
|
|
2425
3245
|
metadata: structuredClone(storedContext.context.metadata),
|
|
@@ -2525,7 +3345,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer) {
|
|
|
2525
3345
|
const persistModelContext = async () => {
|
|
2526
3346
|
const modelContext = {
|
|
2527
3347
|
contextSize: context.contextSize,
|
|
2528
|
-
messages: context.messages,
|
|
3348
|
+
messages: context.messages.map(encodeAgentModelProxyValue),
|
|
2529
3349
|
metadata: context.metadata,
|
|
2530
3350
|
runId: payload.runId,
|
|
2531
3351
|
};
|
|
@@ -2744,6 +3564,18 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer) {
|
|
|
2744
3564
|
if (failure)
|
|
2745
3565
|
throw failure.error;
|
|
2746
3566
|
await persistModelContext();
|
|
3567
|
+
if (input.channelJournal)
|
|
3568
|
+
await appendSessionValue({
|
|
3569
|
+
body: {
|
|
3570
|
+
...input.channelJournal,
|
|
3571
|
+
id: `${input.channelJournal.runId}:${input.channelJournal.attempt}:message`,
|
|
3572
|
+
type: "message.completed",
|
|
3573
|
+
data: agentChannelJsonSchema.parse(JSON.parse(JSON.stringify({ message: responseMessage }))),
|
|
3574
|
+
},
|
|
3575
|
+
chatId: payload.chatId,
|
|
3576
|
+
namespace: input.sessionNamespace,
|
|
3577
|
+
path: "/v2/events",
|
|
3578
|
+
});
|
|
2747
3579
|
};
|
|
2748
3580
|
const coreUsage = (value) => {
|
|
2749
3581
|
const usage = value == null ? null : agentCoreUsageSchema.parse(value);
|
|
@@ -3156,6 +3988,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer) {
|
|
|
3156
3988
|
// abort callbacks and model/tool spans settle before request teardown.
|
|
3157
3989
|
waitUntil(result.consumeStream({ onError: fail }).then(() => finishContext()));
|
|
3158
3990
|
}
|
|
3991
|
+
let channelEventSequence = 0;
|
|
3159
3992
|
const durableStream = stream.pipeThrough(new TransformStream({
|
|
3160
3993
|
async transform(chunk, controller) {
|
|
3161
3994
|
// AI SDK emits finish before awaiting onEnd. Publish success and usage
|
|
@@ -3174,10 +4007,17 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer) {
|
|
|
3174
4007
|
return;
|
|
3175
4008
|
}
|
|
3176
4009
|
await appendSessionValue({
|
|
3177
|
-
body:
|
|
4010
|
+
body: input.channelJournal
|
|
4011
|
+
? {
|
|
4012
|
+
...input.channelJournal,
|
|
4013
|
+
id: `${input.channelJournal.runId}:${input.channelJournal.attempt}:${++channelEventSequence}`,
|
|
4014
|
+
type: chunk.type,
|
|
4015
|
+
data: agentChannelJsonSchema.parse(JSON.parse(JSON.stringify(chunk))),
|
|
4016
|
+
}
|
|
4017
|
+
: { event: chunk },
|
|
3178
4018
|
chatId: payload.chatId,
|
|
3179
4019
|
namespace: input.sessionNamespace,
|
|
3180
|
-
path: "/v1/events",
|
|
4020
|
+
path: input.channelJournal ? "/v2/events" : "/v1/events",
|
|
3181
4021
|
});
|
|
3182
4022
|
controller.enqueue(chunk);
|
|
3183
4023
|
},
|