@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.
Files changed (51) hide show
  1. package/README.md +3 -1253
  2. package/dist/agent-channel-author-runtime.d.ts +79 -0
  3. package/dist/agent-channel-author-runtime.d.ts.map +1 -0
  4. package/dist/agent-channel-author-runtime.js +511 -0
  5. package/dist/agent-channel-author-runtime.js.map +1 -0
  6. package/dist/agent-channel-receiver.d.ts +46 -0
  7. package/dist/agent-channel-receiver.d.ts.map +1 -0
  8. package/dist/agent-channel-receiver.js +356 -0
  9. package/dist/agent-channel-receiver.js.map +1 -0
  10. package/dist/agent-channel-runtime.d.ts +6 -0
  11. package/dist/agent-channel-runtime.d.ts.map +1 -0
  12. package/dist/agent-channel-runtime.js +623 -0
  13. package/dist/agent-channel-runtime.js.map +1 -0
  14. package/dist/agent-channel-worker.d.ts +22 -0
  15. package/dist/agent-channel-worker.d.ts.map +1 -0
  16. package/dist/agent-channel-worker.js +335 -0
  17. package/dist/agent-channel-worker.js.map +1 -0
  18. package/dist/agent-session.d.ts.map +1 -1
  19. package/dist/agent-session.js +177 -4
  20. package/dist/agent-session.js.map +1 -1
  21. package/dist/agent-worker.d.ts +27 -1
  22. package/dist/agent-worker.d.ts.map +1 -1
  23. package/dist/agent-worker.js +867 -27
  24. package/dist/agent-worker.js.map +1 -1
  25. package/dist/channels/feishu-codec.d.ts +18 -0
  26. package/dist/channels/feishu-codec.d.ts.map +1 -0
  27. package/dist/channels/feishu-codec.js +109 -0
  28. package/dist/channels/feishu-codec.js.map +1 -0
  29. package/dist/channels/feishu-inbound.d.ts +35 -0
  30. package/dist/channels/feishu-inbound.d.ts.map +1 -0
  31. package/dist/channels/feishu-inbound.js +136 -0
  32. package/dist/channels/feishu-inbound.js.map +1 -0
  33. package/dist/channels/feishu-websocket.d.ts +5 -0
  34. package/dist/channels/feishu-websocket.d.ts.map +1 -0
  35. package/dist/channels/feishu-websocket.js +424 -0
  36. package/dist/channels/feishu-websocket.js.map +1 -0
  37. package/dist/channels/feishu.d.ts +32 -0
  38. package/dist/channels/feishu.d.ts.map +1 -0
  39. package/dist/channels/feishu.js +233 -0
  40. package/dist/channels/feishu.js.map +1 -0
  41. package/dist/channels/index.d.ts +180 -0
  42. package/dist/channels/index.d.ts.map +1 -0
  43. package/dist/channels/index.js +93 -0
  44. package/dist/channels/index.js.map +1 -0
  45. package/dist/index.d.ts +18 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +38 -8
  48. package/dist/index.js.map +1 -1
  49. package/package.json +16 -3
  50. package/src/channels/README.md +298 -0
  51. package/src/channels/feishu.ts +295 -0
@@ -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 { agentSessionGetModelContextResultSchema, agentSessionAttemptIdentitySchema, agentSessionExecutionStartResultSchema, agentSessionExecutionMutationResultSchema, agentSessionPutModelContextResultSchema, } from "@gea-ai/contract/agent-session";
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 = parsed.data.chatId ?? platformIdentity.chat?.id ?? crypto.randomUUID();
214
- const runId = platformIdentity.run?.id ?? crypto.randomUUID();
215
- const responseMessageId = platformIdentity.responseMessage?.id ??
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 = typeof parsed.data.message === "string"
242
- ? {
243
- id: userMessageId,
244
- role: "user",
245
- parts: [{ type: "text", text: parsed.data.message }],
246
- }
247
- : parsed.data.message.role === "user"
248
- ? { ...parsed.data.message, id: userMessageId }
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
- const runtimeContext = payload.runtimeContext;
2388
- let currentTurn = payload.currentTurn;
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: { event: chunk },
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
  },