@finalai/final-agent-provider 0.2.1 → 0.3.1

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 (38) hide show
  1. package/dist/modules/channel-factories.d.mts +3 -1
  2. package/dist/modules/channel-factories.mjs +32 -18
  3. package/dist/modules/channel-factories.mjs.map +2 -2
  4. package/dist/modules/channels/address-context.mjs +16 -53
  5. package/dist/modules/channels/address-context.mjs.map +3 -3
  6. package/dist/modules/channels/agent-events.d.mts +3 -29
  7. package/dist/modules/channels/agent-events.mjs +67 -146
  8. package/dist/modules/channels/agent-events.mjs.map +3 -3
  9. package/dist/modules/channels/agent-schedules.d.mts +1 -0
  10. package/dist/modules/channels/agent-schedules.mjs +18 -19
  11. package/dist/modules/channels/agent-schedules.mjs.map +2 -2
  12. package/dist/modules/channels/delivery.d.mts +1 -1
  13. package/dist/modules/channels/delivery.mjs +5 -23
  14. package/dist/modules/channels/delivery.mjs.map +2 -2
  15. package/dist/modules/channels/fake-platform.d.mts +6 -1
  16. package/dist/modules/channels/fake-platform.mjs +31 -12
  17. package/dist/modules/channels/fake-platform.mjs.map +2 -2
  18. package/dist/modules/channels/index.d.mts +1 -0
  19. package/dist/modules/channels/index.mjs +1 -0
  20. package/dist/modules/channels/index.mjs.map +2 -2
  21. package/dist/modules/channels/prompt.d.mts +19 -2
  22. package/dist/modules/channels/prompt.mjs +44 -17
  23. package/dist/modules/channels/prompt.mjs.map +2 -2
  24. package/dist/modules/channels/routing.mjs +1 -15
  25. package/dist/modules/channels/routing.mjs.map +2 -2
  26. package/dist/modules/channels/service-input.d.mts +22 -0
  27. package/dist/modules/channels/service-input.mjs +32 -0
  28. package/dist/modules/channels/service-input.mjs.map +7 -0
  29. package/dist/modules/sandboxes/computer.d.mts +2 -0
  30. package/dist/modules/sandboxes/computer.mjs +52 -7
  31. package/dist/modules/sandboxes/computer.mjs.map +3 -3
  32. package/dist/modules/sandboxes/device.d.mts +2 -0
  33. package/dist/modules/sandboxes/device.mjs +26 -3
  34. package/dist/modules/sandboxes/device.mjs.map +2 -2
  35. package/dist/modules/sandboxes/testing/fake-backends.d.mts +3 -0
  36. package/dist/modules/sandboxes/testing/fake-backends.mjs +35 -3
  37. package/dist/modules/sandboxes/testing/fake-backends.mjs.map +2 -2
  38. package/package.json +5 -5
@@ -2,8 +2,7 @@ import {
2
2
  ConversationKind,
3
3
  Delivery,
4
4
  ParticipantKind,
5
- RoutingMode,
6
- SystemParticipantKind
5
+ RoutingMode
7
6
  } from "@finalai/agent-provider/channel";
8
7
  const MAX_AGENT_HOP_DEPTH = 2;
9
8
  const activeAgents = (participants) => participants.filter(
@@ -13,28 +12,15 @@ const effectiveDefaultAgent = (participants) => {
13
12
  const agents = activeAgents(participants);
14
13
  return agents.find((agent) => agent.routingMode === RoutingMode.Default) ?? agents.find((agent) => agent.routingMode === RoutingMode.All) ?? null;
15
14
  };
16
- const observesEverything = (participant) => participant.routingMode === RoutingMode.All || participant.routingMode === RoutingMode.Default;
17
15
  const routeMessage = (args) => {
18
16
  const { conversation, message, participants } = args;
19
- const author = participants.find((participant) => participant.id === message.authorParticipantId) ?? null;
20
17
  const agents = activeAgents(participants).filter(
21
18
  (agent) => agent.id !== message.authorParticipantId && (message.agentAuthorDefinitionId == null || agent.definitionId !== message.agentAuthorDefinitionId)
22
19
  );
23
20
  if (agents.length === 0) {
24
21
  return [];
25
22
  }
26
- const authorKind = author?.kind ?? ParticipantKind.User;
27
23
  const mentioned = new Set(message.mentionedParticipantIds);
28
- if (authorKind === ParticipantKind.System && author?.systemKind !== SystemParticipantKind.ApiKey && message.agentAuthorDefinitionId == null) {
29
- const sourceAgent = author?.definitionId == null ? null : agents.find((agent) => agent.definitionId === author.definitionId) ?? null;
30
- const target = sourceAgent ?? (conversation.kind === ConversationKind.Direct ? agents[0] ?? null : effectiveDefaultAgent(agents));
31
- return agents.flatMap((agent) => {
32
- if (agent.id === target?.id) {
33
- return [{ participantId: agent.id, delivery: Delivery.Prompt }];
34
- }
35
- return observesEverything(agent) ? [{ participantId: agent.id, delivery: Delivery.Observe }] : [];
36
- });
37
- }
38
24
  const mayPrompt = message.hopDepth < MAX_AGENT_HOP_DEPTH;
39
25
  const promptDelivery = mayPrompt ? Delivery.Prompt : Delivery.Observe;
40
26
  if (conversation.kind === ConversationKind.Direct) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/channels/routing.ts"],
4
- "sourcesContent": ["import {\n ConversationKind,\n Delivery,\n ParticipantKind,\n RoutingMode,\n SystemParticipantKind,\n type ParticipantIface,\n type RouteArgsIface,\n type RouteDecisionIface,\n} from '@finalai/agent-provider/channel';\n\n// Agent-authored messages may prompt other agents only while the chain of\n// agent hops that produced them is shorter than this: a human\n// message (0) prompts A, A's reply (1) may prompt B, B's reply (2) prompts no one.\nexport const MAX_AGENT_HOP_DEPTH = 2;\n\nconst activeAgents = (participants: ParticipantIface[]): ParticipantIface[] =>\n participants.filter(\n (participant) => participant.kind === ParticipantKind.Agent && !participant.left,\n );\n\n// The agent a no-mention message goes to: the Default agent, else (no Default)\n// the earliest-joined All agent, which then acts as the Default. Participants\n// arrive in join order.\nexport const effectiveDefaultAgent = (\n participants: ParticipantIface[],\n): ParticipantIface | null => {\n const agents = activeAgents(participants);\n return (\n agents.find((agent) => agent.routingMode === RoutingMode.Default) ??\n agents.find((agent) => agent.routingMode === RoutingMode.All) ??\n null\n );\n};\n\nconst observesEverything = (participant: ParticipantIface): boolean =>\n participant.routingMode === RoutingMode.All || participant.routingMode === RoutingMode.Default;\n\n// Pure: who gets a message and how. Users never receive decisions.\nexport const routeMessage = (args: RouteArgsIface): RouteDecisionIface[] => {\n const { conversation, message, participants } = args;\n const author =\n participants.find((participant) => participant.id === message.authorParticipantId) ?? null;\n const agents = activeAgents(participants).filter(\n (agent) =>\n agent.id !== message.authorParticipantId &&\n (message.agentAuthorDefinitionId == null ||\n agent.definitionId !== message.agentAuthorDefinitionId),\n );\n if (agents.length === 0) {\n return [];\n }\n const authorKind = author?.kind ?? ParticipantKind.User;\n const mentioned = new Set(message.mentionedParticipantIds);\n\n if (\n authorKind === ParticipantKind.System &&\n author?.systemKind !== SystemParticipantKind.ApiKey &&\n message.agentAuthorDefinitionId == null\n ) {\n // The source's agent (schedule or webhook binding) when it is a member,\n // else the Default agent (Goal and tool events).\n const sourceAgent =\n author?.definitionId == null\n ? null\n : (agents.find((agent) => agent.definitionId === author.definitionId) ?? null);\n const target =\n sourceAgent ??\n (conversation.kind === ConversationKind.Direct\n ? (agents[0] ?? null)\n : effectiveDefaultAgent(agents));\n return agents.flatMap((agent): RouteDecisionIface[] => {\n if (agent.id === target?.id) {\n return [{ participantId: agent.id, delivery: Delivery.Prompt }];\n }\n return observesEverything(agent)\n ? [{ participantId: agent.id, delivery: Delivery.Observe }]\n : [];\n });\n }\n\n const mayPrompt = message.hopDepth < MAX_AGENT_HOP_DEPTH;\n const promptDelivery = mayPrompt ? Delivery.Prompt : Delivery.Observe;\n\n if (conversation.kind === ConversationKind.Direct) {\n return agents.map((agent) => ({ participantId: agent.id, delivery: promptDelivery }));\n }\n\n const anyAgentMentioned = agents.some((agent) => mentioned.has(agent.id));\n const defaultAgent = effectiveDefaultAgent(participants);\n return agents.flatMap((agent): RouteDecisionIface[] => {\n if (mentioned.has(agent.id)) {\n return [{ participantId: agent.id, delivery: promptDelivery }];\n }\n if (agent.routingMode === RoutingMode.Mention || agent.routingMode == null) {\n return [];\n }\n if (!anyAgentMentioned && agent.id === defaultAgent?.id) {\n return [{ participantId: agent.id, delivery: promptDelivery }];\n }\n return [{ participantId: agent.id, delivery: Delivery.Observe }];\n });\n};\n"],
5
- "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAKA,MAAM,sBAAsB;AAEnC,MAAM,eAAe,CAAC,iBACpB,aAAa;AAAA,EACX,CAAC,gBAAgB,YAAY,SAAS,gBAAgB,SAAS,CAAC,YAAY;AAC9E;AAKK,MAAM,wBAAwB,CACnC,iBAC4B;AAC5B,QAAM,SAAS,aAAa,YAAY;AACxC,SACE,OAAO,KAAK,CAAC,UAAU,MAAM,gBAAgB,YAAY,OAAO,KAChE,OAAO,KAAK,CAAC,UAAU,MAAM,gBAAgB,YAAY,GAAG,KAC5D;AAEJ;AAEA,MAAM,qBAAqB,CAAC,gBAC1B,YAAY,gBAAgB,YAAY,OAAO,YAAY,gBAAgB,YAAY;AAGlF,MAAM,eAAe,CAAC,SAA+C;AAC1E,QAAM,EAAE,cAAc,SAAS,aAAa,IAAI;AAChD,QAAM,SACJ,aAAa,KAAK,CAAC,gBAAgB,YAAY,OAAO,QAAQ,mBAAmB,KAAK;AACxF,QAAM,SAAS,aAAa,YAAY,EAAE;AAAA,IACxC,CAAC,UACC,MAAM,OAAO,QAAQ,wBACpB,QAAQ,2BAA2B,QAClC,MAAM,iBAAiB,QAAQ;AAAA,EACrC;AACA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AACA,QAAM,aAAa,QAAQ,QAAQ,gBAAgB;AACnD,QAAM,YAAY,IAAI,IAAI,QAAQ,uBAAuB;AAEzD,MACE,eAAe,gBAAgB,UAC/B,QAAQ,eAAe,sBAAsB,UAC7C,QAAQ,2BAA2B,MACnC;AAGA,UAAM,cACJ,QAAQ,gBAAgB,OACpB,OACC,OAAO,KAAK,CAAC,UAAU,MAAM,iBAAiB,OAAO,YAAY,KAAK;AAC7E,UAAM,SACJ,gBACC,aAAa,SAAS,iBAAiB,SACnC,OAAO,CAAC,KAAK,OACd,sBAAsB,MAAM;AAClC,WAAO,OAAO,QAAQ,CAAC,UAAgC;AACrD,UAAI,MAAM,OAAO,QAAQ,IAAI;AAC3B,eAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,SAAS,OAAO,CAAC;AAAA,MAChE;AACA,aAAO,mBAAmB,KAAK,IAC3B,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,SAAS,QAAQ,CAAC,IACxD,CAAC;AAAA,IACP,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,QAAQ,WAAW;AACrC,QAAM,iBAAiB,YAAY,SAAS,SAAS,SAAS;AAE9D,MAAI,aAAa,SAAS,iBAAiB,QAAQ;AACjD,WAAO,OAAO,IAAI,CAAC,WAAW,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,EAAE;AAAA,EACtF;AAEA,QAAM,oBAAoB,OAAO,KAAK,CAAC,UAAU,UAAU,IAAI,MAAM,EAAE,CAAC;AACxE,QAAM,eAAe,sBAAsB,YAAY;AACvD,SAAO,OAAO,QAAQ,CAAC,UAAgC;AACrD,QAAI,UAAU,IAAI,MAAM,EAAE,GAAG;AAC3B,aAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,CAAC;AAAA,IAC/D;AACA,QAAI,MAAM,gBAAgB,YAAY,WAAW,MAAM,eAAe,MAAM;AAC1E,aAAO,CAAC;AAAA,IACV;AACA,QAAI,CAAC,qBAAqB,MAAM,OAAO,cAAc,IAAI;AACvD,aAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,CAAC;AAAA,IAC/D;AACA,WAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,SAAS,QAAQ,CAAC;AAAA,EACjE,CAAC;AACH;",
4
+ "sourcesContent": ["import {\n ConversationKind,\n Delivery,\n ParticipantKind,\n RoutingMode,\n type ParticipantIface,\n type RouteArgsIface,\n type RouteDecisionIface,\n} from '@finalai/agent-provider/channel';\n\n// Agent-authored messages may prompt other agents only while the chain of\n// agent hops that produced them is shorter than this: a human\n// message (0) prompts A, A's reply (1) may prompt B, B's reply (2) prompts no one.\nexport const MAX_AGENT_HOP_DEPTH = 2;\n\nconst activeAgents = (participants: ParticipantIface[]): ParticipantIface[] =>\n participants.filter(\n (participant) => participant.kind === ParticipantKind.Agent && !participant.left,\n );\n\n// The agent a no-mention message goes to: the Default agent, else (no Default)\n// the earliest-joined All agent, which then acts as the Default. Participants\n// arrive in join order.\nexport const effectiveDefaultAgent = (\n participants: ParticipantIface[],\n): ParticipantIface | null => {\n const agents = activeAgents(participants);\n return (\n agents.find((agent) => agent.routingMode === RoutingMode.Default) ??\n agents.find((agent) => agent.routingMode === RoutingMode.All) ??\n null\n );\n};\n\n// Pure: who gets a message and how. Users never receive decisions.\nexport const routeMessage = (args: RouteArgsIface): RouteDecisionIface[] => {\n const { conversation, message, participants } = args;\n const agents = activeAgents(participants).filter(\n (agent) =>\n agent.id !== message.authorParticipantId &&\n (message.agentAuthorDefinitionId == null ||\n agent.definitionId !== message.agentAuthorDefinitionId),\n );\n if (agents.length === 0) {\n return [];\n }\n const mentioned = new Set(message.mentionedParticipantIds);\n const mayPrompt = message.hopDepth < MAX_AGENT_HOP_DEPTH;\n const promptDelivery = mayPrompt ? Delivery.Prompt : Delivery.Observe;\n\n if (conversation.kind === ConversationKind.Direct) {\n return agents.map((agent) => ({ participantId: agent.id, delivery: promptDelivery }));\n }\n\n const anyAgentMentioned = agents.some((agent) => mentioned.has(agent.id));\n const defaultAgent = effectiveDefaultAgent(participants);\n return agents.flatMap((agent): RouteDecisionIface[] => {\n if (mentioned.has(agent.id)) {\n return [{ participantId: agent.id, delivery: promptDelivery }];\n }\n if (agent.routingMode === RoutingMode.Mention || agent.routingMode == null) {\n return [];\n }\n if (!anyAgentMentioned && agent.id === defaultAgent?.id) {\n return [{ participantId: agent.id, delivery: promptDelivery }];\n }\n return [{ participantId: agent.id, delivery: Delivery.Observe }];\n });\n};\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAKA,MAAM,sBAAsB;AAEnC,MAAM,eAAe,CAAC,iBACpB,aAAa;AAAA,EACX,CAAC,gBAAgB,YAAY,SAAS,gBAAgB,SAAS,CAAC,YAAY;AAC9E;AAKK,MAAM,wBAAwB,CACnC,iBAC4B;AAC5B,QAAM,SAAS,aAAa,YAAY;AACxC,SACE,OAAO,KAAK,CAAC,UAAU,MAAM,gBAAgB,YAAY,OAAO,KAChE,OAAO,KAAK,CAAC,UAAU,MAAM,gBAAgB,YAAY,GAAG,KAC5D;AAEJ;AAGO,MAAM,eAAe,CAAC,SAA+C;AAC1E,QAAM,EAAE,cAAc,SAAS,aAAa,IAAI;AAChD,QAAM,SAAS,aAAa,YAAY,EAAE;AAAA,IACxC,CAAC,UACC,MAAM,OAAO,QAAQ,wBACpB,QAAQ,2BAA2B,QAClC,MAAM,iBAAiB,QAAQ;AAAA,EACrC;AACA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AACA,QAAM,YAAY,IAAI,IAAI,QAAQ,uBAAuB;AACzD,QAAM,YAAY,QAAQ,WAAW;AACrC,QAAM,iBAAiB,YAAY,SAAS,SAAS,SAAS;AAE9D,MAAI,aAAa,SAAS,iBAAiB,QAAQ;AACjD,WAAO,OAAO,IAAI,CAAC,WAAW,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,EAAE;AAAA,EACtF;AAEA,QAAM,oBAAoB,OAAO,KAAK,CAAC,UAAU,UAAU,IAAI,MAAM,EAAE,CAAC;AACxE,QAAM,eAAe,sBAAsB,YAAY;AACvD,SAAO,OAAO,QAAQ,CAAC,UAAgC;AACrD,QAAI,UAAU,IAAI,MAAM,EAAE,GAAG;AAC3B,aAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,CAAC;AAAA,IAC/D;AACA,QAAI,MAAM,gBAAgB,YAAY,WAAW,MAAM,eAAe,MAAM;AAC1E,aAAO,CAAC;AAAA,IACV;AACA,QAAI,CAAC,qBAAqB,MAAM,OAAO,cAAc,IAAI;AACvD,aAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,eAAe,CAAC;AAAA,IAC/D;AACA,WAAO,CAAC,EAAE,eAAe,MAAM,IAAI,UAAU,SAAS,QAAQ,CAAC;AAAA,EACjE,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,22 @@
1
+ import type { ContentBlock } from '@agentclientprotocol/sdk/experimental/v2';
2
+ import { ChannelPrincipalType, type ChannelAuthIface, type ChannelInputReceiptIface, type ChannelPlatformIface, type JsonObject, type TurnPolicy } from '@finalai/agent-provider';
3
+ export interface ServiceInputArgsIface {
4
+ room: string;
5
+ participantId?: string;
6
+ content: ContentBlock[];
7
+ principal: {
8
+ principalId: string;
9
+ principalType?: ChannelPrincipalType | `${ChannelPrincipalType}`;
10
+ authenticator: string;
11
+ attributes?: JsonObject;
12
+ };
13
+ displayName: string;
14
+ actingUserId?: string;
15
+ originTurnId?: string;
16
+ idempotencyKey: string;
17
+ label: string;
18
+ context?: string[];
19
+ turnPolicy?: TurnPolicy | `${TurnPolicy}`;
20
+ }
21
+ export declare const serviceAuthOf: (args: ServiceInputArgsIface) => ChannelAuthIface;
22
+ export declare const sendServiceInput: (platform: Pick<ChannelPlatformIface, "channelSessions">, args: ServiceInputArgsIface) => Promise<ChannelInputReceiptIface>;
@@ -0,0 +1,32 @@
1
+ import {
2
+ ChannelPrincipalType
3
+ } from "@finalai/agent-provider";
4
+ const serviceAuthOf = (args) => ({
5
+ principalId: args.principal.principalId,
6
+ principalType: args.principal.principalType ?? ChannelPrincipalType.Service,
7
+ authenticator: args.principal.authenticator,
8
+ displayName: args.displayName,
9
+ attributes: args.principal.attributes ?? {}
10
+ });
11
+ const sendServiceInput = async (platform, args) => {
12
+ if (platform.channelSessions == null)
13
+ throw new Error("channel session operations are unavailable");
14
+ return platform.channelSessions.send(
15
+ { conversationId: args.room, participantId: args.participantId },
16
+ {
17
+ content: args.content,
18
+ idempotencyKey: args.idempotencyKey,
19
+ auth: serviceAuthOf(args),
20
+ actingUserId: args.actingUserId,
21
+ originTurnId: args.originTurnId,
22
+ source: { label: args.label },
23
+ context: args.context,
24
+ turnPolicy: args.turnPolicy
25
+ }
26
+ );
27
+ };
28
+ export {
29
+ sendServiceInput,
30
+ serviceAuthOf
31
+ };
32
+ //# sourceMappingURL=service-input.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/channels/service-input.ts"],
4
+ "sourcesContent": ["import type { ContentBlock } from '@agentclientprotocol/sdk/experimental/v2';\nimport {\n ChannelPrincipalType,\n type ChannelAuthIface,\n type ChannelInputReceiptIface,\n type ChannelPlatformIface,\n type JsonObject,\n type TurnPolicy,\n} from '@finalai/agent-provider';\n\n// Input to a Final room's agent from no member of the room: a schedule\n// firing, a webhook, a linked conversation, a sandbox result, a handoff from\n// another channel. It is channel input from a principal, never a message: the\n// platform accepts it at once and prompts the agent; the agent's reply in the\n// room carries `label` as its caption.\n\nexport interface ServiceInputArgsIface {\n // The room (conversation id).\n room: string;\n // One agent of the room; the room's Default agent when absent.\n participantId?: string;\n content: ContentBlock[];\n // Who sent it. A service principal unless a channel authenticated a person.\n principal: {\n principalId: string;\n principalType?: ChannelPrincipalType | `${ChannelPrincipalType}`;\n authenticator: string;\n attributes?: JsonObject;\n };\n // How the agent and the room name the principal.\n displayName: string;\n // The person the input acts for: who pays for the turn and answers what it asks.\n actingUserId?: string;\n // The turn whose work produced the input, for loop limits and reply provenance.\n originTurnId?: string;\n idempotencyKey: string;\n // The reply's caption (\"After the 9:00 schedule\"), written by the producer.\n label: string;\n context?: string[];\n turnPolicy?: TurnPolicy | `${TurnPolicy}`;\n}\n\nexport const serviceAuthOf = (args: ServiceInputArgsIface): ChannelAuthIface => ({\n principalId: args.principal.principalId,\n principalType: args.principal.principalType ?? ChannelPrincipalType.Service,\n authenticator: args.principal.authenticator,\n displayName: args.displayName,\n attributes: args.principal.attributes ?? {},\n});\n\nexport const sendServiceInput = async (\n platform: Pick<ChannelPlatformIface, 'channelSessions'>,\n args: ServiceInputArgsIface,\n): Promise<ChannelInputReceiptIface> => {\n if (platform.channelSessions == null)\n throw new Error('channel session operations are unavailable');\n return platform.channelSessions.send(\n { conversationId: args.room, participantId: args.participantId },\n {\n content: args.content,\n idempotencyKey: args.idempotencyKey,\n auth: serviceAuthOf(args),\n actingUserId: args.actingUserId,\n originTurnId: args.originTurnId,\n source: { label: args.label },\n context: args.context,\n turnPolicy: args.turnPolicy,\n },\n );\n};\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,OAMK;AAkCA,MAAM,gBAAgB,CAAC,UAAmD;AAAA,EAC/E,aAAa,KAAK,UAAU;AAAA,EAC5B,eAAe,KAAK,UAAU,iBAAiB,qBAAqB;AAAA,EACpE,eAAe,KAAK,UAAU;AAAA,EAC9B,aAAa,KAAK;AAAA,EAClB,YAAY,KAAK,UAAU,cAAc,CAAC;AAC5C;AAEO,MAAM,mBAAmB,OAC9B,UACA,SACsC;AACtC,MAAI,SAAS,mBAAmB;AAC9B,UAAM,IAAI,MAAM,4CAA4C;AAC9D,SAAO,SAAS,gBAAgB;AAAA,IAC9B,EAAE,gBAAgB,KAAK,MAAM,eAAe,KAAK,cAAc;AAAA,IAC/D;AAAA,MACE,SAAS,KAAK;AAAA,MACd,gBAAgB,KAAK;AAAA,MACrB,MAAM,cAAc,IAAI;AAAA,MACxB,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK;AAAA,MACnB,QAAQ,EAAE,OAAO,KAAK,MAAM;AAAA,MAC5B,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,IACnB;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -29,6 +29,8 @@ export interface ComputerSandboxProviderConfigIface {
29
29
  buildPollMs?: number;
30
30
  shelveWaitMs?: number;
31
31
  shelvePollMs?: number;
32
+ runningWaitMs?: number;
33
+ runningPollMs?: number;
32
34
  log?: ComputerSandboxLogIface;
33
35
  randomUuid?: () => string;
34
36
  now?: () => number;
@@ -11,6 +11,7 @@ import {
11
11
  PLATFORM_ENVIRONMENT_NAMES,
12
12
  platformProcessEnvironment,
13
13
  PortProtocol,
14
+ reportPlatformNetworkPolicy,
14
15
  SandboxCapability,
15
16
  sandboxSeedArtifact,
16
17
  sandboxSeedArtifactSchema,
@@ -134,12 +135,12 @@ const CAPTURE_SCRUBBED_PATHS = [
134
135
  "/home/user/.ssh"
135
136
  ];
136
137
  const BUILDER_IDLE_TIMEOUT_SECONDS = 2 * 60 * 60;
137
- const egressFor = (ctx, requested) => {
138
- const policy = withPlatformEgressHosts(ctx, requested);
138
+ const computerEgressOf = (policy) => {
139
139
  if (policy.mode === NetworkPolicy.AllowAll) return { egress: ComputerEgressPolicy.Internet };
140
140
  if (policy.mode === NetworkPolicy.DenyAll) return { egress: ComputerEgressPolicy.None };
141
141
  return { egress: ComputerEgressPolicy.Allowlist, allow: policy.allow ?? [] };
142
142
  };
143
+ const egressFor = (ctx, requested) => computerEgressOf(withPlatformEgressHosts(ctx, requested));
143
144
  const LINEAGE_PREFIX = "lineage:";
144
145
  const portOf = (url) => {
145
146
  try {
@@ -157,6 +158,7 @@ const sandboxWorkspaceOf = {
157
158
  [ComputerWorkspacePolicy.Ephemeral]: SandboxWorkspace.Ephemeral
158
159
  };
159
160
  const isRunningState = (state) => state === ComputerState.Running || state === ComputerState.Paused || state === ComputerState.Resuming;
161
+ const RUNNING_POLL_MAX_MS = 5e3;
160
162
  const createComputerSandboxProvider = (config) => {
161
163
  const log = config.log ?? (() => void 0);
162
164
  const randomUuid = config.randomUuid ?? (() => crypto.randomUUID());
@@ -164,6 +166,7 @@ const createComputerSandboxProvider = (config) => {
164
166
  const sleep = config.sleep ?? defaultSleep;
165
167
  const defaultImage = config.defaultImage ?? DEFAULT_COMPUTER_IMAGE;
166
168
  const credentials = createComputersApiCredentials(config.computersApi, now);
169
+ const runningWaitMs = config.runningWaitMs ?? 5 * 6e4;
167
170
  const clientFor = (organizationId) => createComputersApiClient({ config: config.computersApi, credentials, organizationId });
168
171
  const registerImage = (client, reference) => client.registerImage(
169
172
  config.registryCredentialId == null ? { reference } : { reference, registryCredentialId: config.registryCredentialId }
@@ -435,6 +438,29 @@ ${commandHelp}`}`,
435
438
  await sleep(config.buildPollMs ?? 5e3);
436
439
  }
437
440
  };
441
+ const awaitComputerRunning = async (client, computerId, timeoutMs) => {
442
+ const deadline = now() + timeoutMs;
443
+ let pollMs = config.runningPollMs ?? 250;
444
+ for (; ; ) {
445
+ const computer = await client.getComputer(computerId);
446
+ if (computer == null || computer.state === ComputerState.Deleted)
447
+ throw computerSandboxError("computer_gone", `computer ${computerId} is gone`);
448
+ if (computer.state === ComputerState.Error)
449
+ throw computerSandboxError(
450
+ "computer_gone",
451
+ `computer ${computer.name} is in the Error state`
452
+ );
453
+ if (computer.state === ComputerState.Running) return computer;
454
+ const left = deadline - now();
455
+ if (left <= 0)
456
+ throw computerSandboxError(
457
+ "computer_not_running",
458
+ `computer ${computer.name} was not running within ${Math.round(timeoutMs / 1e3)} s (it is ${ComputerState[computer.state]})`
459
+ );
460
+ await sleep(Math.min(pollMs, left));
461
+ pollMs = Math.min(pollMs * 2, RUNNING_POLL_MAX_MS);
462
+ }
463
+ };
438
464
  const awaitShelved = async (client, computerId) => {
439
465
  const deadline = now() + (config.shelveWaitMs ?? 5 * 6e4);
440
466
  for (; ; ) {
@@ -475,6 +501,7 @@ ${commandHelp}`}`,
475
501
  );
476
502
  ctx.log?.(`building computer base ${key} on ${builder.name}`);
477
503
  try {
504
+ await awaitComputerRunning(client, builder.id, runningWaitMs);
478
505
  const sandbox = setupSession({ client, info: builder, env: async () => ({}) }).sandbox;
479
506
  const prepared = await sandbox.run({
480
507
  command: "/bin/sh",
@@ -579,6 +606,11 @@ ${step.run}`,
579
606
  log
580
607
  });
581
608
  };
609
+ const awaitRunning = (ctx, rawState, options2) => awaitComputerRunning(
610
+ clientFor(ctx.session.organizationId),
611
+ computerStateSchema.pick({ computerId: true }).parse(rawState).computerId,
612
+ options2.timeoutMs
613
+ );
582
614
  return {
583
615
  processes: async (ctx, rawState, request) => {
584
616
  const state = computerStateSchema.parse(rawState);
@@ -677,10 +709,14 @@ ${step.run}`,
677
709
  base: { kind: "image", image: spec.image, imageVersionId }
678
710
  };
679
711
  },
712
+ awaitRunning: async (ctx, state, options2) => {
713
+ await awaitRunning(ctx, state, options2);
714
+ },
680
715
  start: async (ctx, openOptions, rawArtifact) => {
681
716
  const access = computerOpenOptionsSchema.parse(openOptions ?? {}).access;
682
717
  const spec = await resolve(ctx.guides);
683
- const egress = egressFor(ctx, spec.parameters.networkPolicy);
718
+ const policy = withPlatformEgressHosts(ctx, spec.parameters.networkPolicy);
719
+ const egress = computerEgressOf(policy);
684
720
  const artifact = computerArtifactSchema.safeParse(rawArtifact);
685
721
  if (!artifact.success) {
686
722
  throw computerSandboxError(
@@ -736,8 +772,12 @@ ${step.run}`,
736
772
  const idempotencyKey = `agent-sandbox-${(await sha256Hex(`${ctx.session.bindingId}:${name}:${source.baseId ?? source.imageVersionId}`)).slice(0, 32)}`;
737
773
  info = await client.createComputer(body, idempotencyKey);
738
774
  }
739
- log("computer_sandbox.started", { computerId: info.id, reused: existing != null });
775
+ const reused = existing != null && existing.id === info.id;
776
+ log("computer_sandbox.started", { computerId: info.id, reused });
740
777
  try {
778
+ info = await awaitRunning(ctx, { computerId: info.id }, { timeoutMs: runningWaitMs });
779
+ if (reused) await client.setNetworkPolicy(info.id, egress);
780
+ await reportPlatformNetworkPolicy(ctx, policy);
741
781
  const setup = setupSession({ client, info, env });
742
782
  await writePlatformFiles(ctx, setup.sandbox);
743
783
  await mountRepositories({
@@ -774,7 +814,7 @@ ${step.run}`,
774
814
  }
775
815
  };
776
816
  } catch (error) {
777
- if (existing == null || existing.id !== info.id) {
817
+ if (!reused) {
778
818
  try {
779
819
  await client.action(info.id, "delete");
780
820
  } catch (cleanupError) {
@@ -796,6 +836,7 @@ ${step.run}`,
796
836
  "computer_not_running",
797
837
  "computer is not running for passive access"
798
838
  );
839
+ await awaitRunning(ctx, state, { timeoutMs: runningWaitMs });
799
840
  const spec = await resolve(ctx.guides);
800
841
  const env = platformProcessEnvironment(ctx, await loadVariables(ctx, spec));
801
842
  return (await buildHandle({ ctx, client, info, spec, access: state.access, env })).handle;
@@ -810,16 +851,20 @@ ${step.run}`,
810
851
  const rebooted = !isRunningState(info.state);
811
852
  if (info.state === ComputerState.Paused) await client.action(info.id, "resume");
812
853
  else if (rebooted) await client.action(info.id, "start");
854
+ const running = await awaitRunning(ctx, state, { timeoutMs: runningWaitMs });
813
855
  const spec = await resolve(ctx.guides);
814
856
  const env = platformProcessEnvironment(ctx, await loadVariables(ctx, spec));
815
- const setup = setupSession({ client, info, env });
857
+ const policy = withPlatformEgressHosts(ctx, spec.parameters.networkPolicy);
858
+ await client.setNetworkPolicy(running.id, computerEgressOf(policy));
859
+ await reportPlatformNetworkPolicy(ctx, policy);
860
+ const setup = setupSession({ client, info: running, env });
816
861
  await writePlatformFiles(ctx, setup.sandbox);
817
862
  await installGuideCommands({ sandbox: setup.sandbox, template: spec.template });
818
863
  if (rebooted) await startServicesFor({ setup, spec, env });
819
864
  const { handle } = await buildHandle({
820
865
  ctx,
821
866
  client,
822
- info,
867
+ info: running,
823
868
  spec,
824
869
  access: state.access,
825
870
  env