@finalai/final-agent-provider 0.5.0 → 0.6.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 (30) hide show
  1. package/HOSTING.md +3 -3
  2. package/README.md +4 -0
  3. package/WORKSPACE-EGRESS.md +7 -6
  4. package/dist/modules/channels/projection.d.mts +13 -7
  5. package/dist/modules/channels/projection.mjs +49 -31
  6. package/dist/modules/channels/projection.mjs.map +2 -2
  7. package/dist/modules/sandboxes/computer.d.mts +4 -3
  8. package/dist/modules/sandboxes/computer.mjs +22 -35
  9. package/dist/modules/sandboxes/computer.mjs.map +2 -2
  10. package/dist/modules/sandboxes/computers-api.d.mts +2 -17
  11. package/dist/modules/sandboxes/computers-api.mjs +27 -44
  12. package/dist/modules/sandboxes/computers-api.mjs.map +2 -2
  13. package/dist/modules/sandboxes/device-api.d.mts +3 -8
  14. package/dist/modules/sandboxes/device-api.mjs +17 -28
  15. package/dist/modules/sandboxes/device-api.mjs.map +3 -3
  16. package/dist/modules/sandboxes/device.d.mts +2 -1
  17. package/dist/modules/sandboxes/device.mjs +13 -5
  18. package/dist/modules/sandboxes/device.mjs.map +2 -2
  19. package/dist/modules/sandboxes/egress.d.mts +4 -1
  20. package/dist/modules/sandboxes/egress.mjs +20 -2
  21. package/dist/modules/sandboxes/egress.mjs.map +2 -2
  22. package/dist/modules/sandboxes/environment.d.mts +0 -3
  23. package/dist/modules/sandboxes/environment.mjs +2 -8
  24. package/dist/modules/sandboxes/environment.mjs.map +2 -2
  25. package/dist/modules/sandboxes/index.d.mts +1 -1
  26. package/dist/modules/sandboxes/index.mjs +0 -2
  27. package/dist/modules/sandboxes/index.mjs.map +2 -2
  28. package/dist/modules/sandboxes/testing/fake-backends.mjs +2 -2
  29. package/dist/modules/sandboxes/testing/fake-backends.mjs.map +2 -2
  30. package/package.json +5 -5
package/HOSTING.md CHANGED
@@ -7,13 +7,13 @@ resolve against dev and production configuration without source edits.
7
7
  ## Selecting a deployment
8
8
 
9
9
  ```sh
10
- export FINAL_API_HOST=https://agent-session.primary-dev.final.ai/api
11
- export FINAL_API_KEY=<dev-organization-key>
10
+ export FINAL_API_HOST=https://<dev-agents-api-host>/api
11
+ export FINAL_API_KEY=<dev-api-key>
12
12
  pnpm exec final-agents build ./agent
13
13
  pnpm exec final-agents publish ./agent --agent my-agent
14
14
  ```
15
15
 
16
- Choose production by changing the host and organization key. `--host` overrides
16
+ Choose production by changing the host and API key. `--host` overrides
17
17
  `FINAL_API_HOST`; without either, the existing production default remains. Build
18
18
  compiles locally and optionally checks installed-harness compatibility. Publish
19
19
  compiles again and sends source/release requests to the selected deployment.
package/README.md CHANGED
@@ -8,6 +8,10 @@ Optional Node ACP adapters are exposed under `/runtime/*`. Sandbox adapters use
8
8
  the public Computers and Device APIs; the Computers transport is shared with
9
9
  `@finalai/computers`. Runtime configuration and credentials are injected.
10
10
 
11
+ The `computer` and `device` sandboxes authenticate with the context's
12
+ `credentials.computersAccess()` and `credentials.deviceAccess()` (`{ baseUrl, token }`);
13
+ without them they fail with `credential_missing`.
14
+
11
15
  Authoring helpers produce manifest bindings. Select the matching `/runtime/*`
12
16
  adapter when connecting your own CLI or ACP endpoint.
13
17
 
@@ -12,12 +12,12 @@ a public replacement-Device protocol. Compose it with a provider through the sam
12
12
  ```ts
13
13
  const networking = createWorkspaceDeviceNetworking({ api: deviceApi, needed });
14
14
  const computers = createComputerSandboxProvider({ computersApi, networking });
15
- const daytona = createDaytonaSandboxProvider({ credential: daytonaCredential, networking });
15
+ const daytona = createDaytonaSandboxProvider({ networking });
16
16
  ```
17
17
 
18
18
  The factories are exported from `/sandboxes/egress`, `/sandboxes/computer`, and
19
19
  `@finalai/daytona-agent-provider/sandboxes/daytona`, respectively. Keep the trusted
20
- Device credential callback in host composition. A custom provider accepts
20
+ Device access in the context's credentials. A custom provider accepts
21
21
  the same generic extension: call `enabled`, validate its workspace identity, call
22
22
  `prepare` before provider fencing, and use `attachSandboxNetworking` after
23
23
  obtaining its raw handle. Supply the saved policy, restore/fence callbacks,
@@ -25,19 +25,20 @@ namespace existence check and deferred setup. Physical teardown calls `suspend`
25
25
  even if another cleanup step fails. Providers needing earlier namespace setup can
26
26
  use `controller` and `withSandboxNetworking` directly, as Final Computer does.
27
27
 
28
- Device credential callbacks receive the organization and `actingUserId`. Device
28
+ The context's `credentials.deviceAccess({ actingUserId })` returns the Device API
29
+ origin and a token. Device
29
30
  operations use the current actor; background lease reconciliation may use the stored
30
31
  lease owner instead of a present turn author. Hosts must authorize both forms of work.
31
32
  Admission, policy persistence, and cleanup also make trusted service calls with
32
- `actingUserId: null`. Enabling workspace Device networking requires a credential
33
+ `actingUserId: null`. Enabling workspace Device networking requires access
33
34
  accepted by those service endpoints for that case, separate from user-bound grants.
34
35
  Returning `null` refuses the call before HTTP; a user-token-only configuration cannot
35
36
  start or clean up networked workspaces. Its host must add service authentication
36
37
  before enabling this extension. The SDK never reuses a user's cached grant for a
37
- service call, another user, organization, or API origin.
38
+ service call or another user: it asks the context's credentials on every call.
38
39
 
39
40
  The extension owns Device authorization, enrollment, helper installation, leases
40
- and failover. Providers need no Device or Headscale implementation. Its generic
41
+ and failover. Providers need no Device networking implementation. Its generic
41
42
  controller also authorizes external editor access and reports its expiry limit;
42
43
  providers must bound editor grants by that deadline. The Device executable and
43
44
  hosted control services remain separate first-party implementations.
@@ -3,13 +3,19 @@ import { TurnPolicy, type JsonValue, type ProjectionContextIface } from '@finala
3
3
  import * as z from 'zod';
4
4
  export declare const DEFAULT_PROJECTION_UPDATE_INTERVAL_MS = 300;
5
5
  export declare const projectionStateSchema: z.ZodObject<{
6
- messageId: z.ZodNullable<z.ZodString>;
6
+ messageId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
7
7
  messageSeq: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
8
- fromSeq: z.ZodNullable<z.ZodNumber>;
9
- lastSeq: z.ZodNullable<z.ZodNumber>;
10
- lastFlushAt: z.ZodNumber;
11
- dirty: z.ZodBoolean;
8
+ fromSeq: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
9
+ lastSeq: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
10
+ lastFlushAt: z.ZodDefault<z.ZodNumber>;
11
+ dirty: z.ZodDefault<z.ZodBoolean>;
12
12
  segments: z.ZodArray<z.ZodObject<{
13
+ messageId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
14
+ messageSeq: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
15
+ fromSeq: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
16
+ lastSeq: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
17
+ lastFlushAt: z.ZodDefault<z.ZodNumber>;
18
+ dirty: z.ZodDefault<z.ZodBoolean>;
13
19
  acpMessageId: z.ZodString;
14
20
  blocks: z.ZodArray<z.ZodType<import("@finalai/agent-provider").JsonObject, unknown, z.core.$ZodTypeInternals<import("@finalai/agent-provider").JsonObject, unknown>>>;
15
21
  }, z.core.$strip>>;
@@ -22,8 +28,8 @@ export interface ProjectorConfigIface {
22
28
  }
23
29
  export declare const emptyProjectionState: () => ProjectionStateIface;
24
30
  export declare const projectionStateOf: (state: JsonValue | null) => ProjectionStateIface;
25
- export declare const visibleContent: (state: ProjectionStateIface) => ContentBlock[];
26
- export declare const accumulateUpdate: (state: ProjectionStateIface, update: SessionUpdate) => boolean;
31
+ export declare const visibleContent: (state: Pick<ProjectionStateIface, "segments">) => ContentBlock[];
32
+ export declare const accumulateUpdate: (state: ProjectionStateIface, update: SessionUpdate) => ProjectionStateIface["segments"][number] | null;
27
33
  export interface ProjectorIface {
28
34
  project(update: SessionUpdate, ctx: ProjectionContextIface): Promise<void>;
29
35
  }
@@ -8,16 +8,21 @@ import {
8
8
  import * as z from "zod";
9
9
  import { contentBlockOf, jsonOfContentBlock } from "./content.mjs";
10
10
  const DEFAULT_PROJECTION_UPDATE_INTERVAL_MS = 300;
11
- const segmentSchema = z.object({ acpMessageId: z.string(), blocks: z.array(jsonObjectSchema) });
12
- const projectionStateSchema = z.object({
13
- messageId: z.string().nullable(),
11
+ const projectedMessageSchema = z.object({
12
+ messageId: z.string().nullable().default(null),
14
13
  // The reply's conversation seq, so the finished reply is read back by
15
14
  // position however many messages arrived meanwhile.
16
15
  messageSeq: z.number().nullable().default(null),
17
- fromSeq: z.number().nullable(),
18
- lastSeq: z.number().nullable(),
19
- lastFlushAt: z.number(),
20
- dirty: z.boolean(),
16
+ fromSeq: z.number().nullable().default(null),
17
+ lastSeq: z.number().nullable().default(null),
18
+ lastFlushAt: z.number().default(0),
19
+ dirty: z.boolean().default(false)
20
+ });
21
+ const segmentSchema = projectedMessageSchema.extend({
22
+ acpMessageId: z.string(),
23
+ blocks: z.array(jsonObjectSchema)
24
+ });
25
+ const projectionStateSchema = projectedMessageSchema.extend({
21
26
  segments: z.array(segmentSchema)
22
27
  });
23
28
  const agentChunkSchema = z.object({
@@ -79,7 +84,7 @@ const segmentFor = (state, acpMessageId) => {
79
84
  if (existing != null) {
80
85
  return existing;
81
86
  }
82
- const segment = { acpMessageId, blocks: [] };
87
+ const segment = segmentSchema.parse({ acpMessageId, blocks: [] });
83
88
  state.segments.push(segment);
84
89
  return segment;
85
90
  };
@@ -88,35 +93,39 @@ const accumulateUpdate = (state, update) => {
88
93
  const chunk = agentChunkSchema.safeParse(json);
89
94
  if (chunk.success) {
90
95
  const block = contentBlockOf(chunk.data.content);
91
- if (block == null) return false;
92
- segmentFor(state, chunk.data.messageId).blocks.push(jsonOfContentBlock(block));
93
- return true;
96
+ if (block == null) return null;
97
+ const segment = segmentFor(state, chunk.data.messageId);
98
+ segment.blocks.push(jsonOfContentBlock(block));
99
+ return segment;
94
100
  }
95
101
  const message = agentMessageSchema.safeParse(json);
96
102
  if (message.success) {
97
- if (message.data.content === void 0) return false;
103
+ if (message.data.content === void 0) return null;
98
104
  const blocks = (message.data.content ?? []).flatMap((block) => {
99
105
  const parsed = contentBlockOf(block);
100
106
  return parsed == null ? [] : [jsonOfContentBlock(parsed)];
101
107
  });
102
- segmentFor(state, message.data.messageId).blocks = blocks;
103
- return true;
108
+ const segment = segmentFor(state, message.data.messageId);
109
+ segment.blocks = blocks;
110
+ return segment;
104
111
  }
105
- return false;
112
+ return null;
106
113
  };
107
114
  const createProjector = (config = {}) => {
108
115
  const now = config.now ?? Date.now;
109
116
  const interval = config.updateIntervalMs ?? DEFAULT_PROJECTION_UPDATE_INTERVAL_MS;
110
117
  const fold = async (update, ctx) => {
111
118
  const state = projectionStateOf(ctx.state.get());
112
- if (!accumulateUpdate(state, update)) return;
113
- state.lastSeq = ctx.seq;
114
- const content = visibleContent(state);
115
- if (content.length === 0) {
119
+ const segment = accumulateUpdate(state, update);
120
+ if (segment == null) return;
121
+ const target = state.messageId == null ? segment : state;
122
+ target.lastSeq = ctx.seq;
123
+ const content = visibleContent({ segments: target === state ? state.segments : [segment] });
124
+ if (content.length === 0 && target.messageId == null) {
116
125
  ctx.state.set(state);
117
126
  return;
118
127
  }
119
- if (state.messageId == null) {
128
+ if (target.messageId == null) {
120
129
  const message = await ctx.platform.conversations.postMessage({
121
130
  conversationId: ctx.conversation.conversationId,
122
131
  authorParticipantId: ctx.agentParticipant.id,
@@ -124,32 +133,41 @@ const createProjector = (config = {}) => {
124
133
  content,
125
134
  source: { turnId: ctx.turn.id, fromSeq: ctx.seq, toSeq: ctx.seq }
126
135
  });
127
- state.messageId = message.messageId;
128
- state.messageSeq = message.seq;
129
- state.fromSeq = ctx.seq;
130
- state.lastFlushAt = now();
131
- state.dirty = false;
132
- } else if (now() - state.lastFlushAt >= interval) {
136
+ target.messageId = message.messageId;
137
+ target.messageSeq = message.seq;
138
+ target.fromSeq = ctx.seq;
139
+ target.lastFlushAt = now();
140
+ target.dirty = false;
141
+ } else if (now() - target.lastFlushAt >= interval) {
133
142
  await ctx.platform.conversations.updateMessage({
134
- messageId: state.messageId,
143
+ messageId: target.messageId,
135
144
  content,
136
145
  final: false
137
146
  });
138
- state.lastFlushAt = now();
139
- state.dirty = false;
147
+ target.lastFlushAt = now();
148
+ target.dirty = false;
140
149
  } else {
141
- state.dirty = true;
150
+ target.dirty = true;
142
151
  }
143
152
  ctx.state.set(state);
144
153
  };
145
154
  const finish = async (ctx) => {
146
155
  const state = projectionStateOf(ctx.state.get());
156
+ if (state.messageId != null) {
157
+ await finishMessage(ctx, state, visibleContent(state));
158
+ return;
159
+ }
160
+ for (const segment of state.segments) {
161
+ await finishMessage(ctx, segment, visibleContent({ segments: [segment] }));
162
+ }
163
+ };
164
+ const finishMessage = async (ctx, state, content) => {
147
165
  if (state.messageId == null) {
148
166
  return;
149
167
  }
150
168
  await ctx.platform.conversations.updateMessage({
151
169
  messageId: state.messageId,
152
- content: visibleContent(state),
170
+ content,
153
171
  final: true
154
172
  });
155
173
  const reply = state.messageSeq == null ? null : (await ctx.platform.conversations.recentMessages({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/channels/projection.ts"],
4
- "sourcesContent": ["import type { ContentBlock, SessionUpdate } from '@agentclientprotocol/sdk/experimental/v2';\nimport { textOfBlock } from '@finalai/acp-session';\nimport {\n jsonObjectSchema,\n MessageKind,\n turnEndOf,\n TurnPolicy,\n type JsonValue,\n type ProjectionContextIface,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport { contentBlockOf, jsonOfContentBlock } from \"./content.mjs\";\n\n// agent_message_chunk accumulates into one visible message per turn\n// segment. The message is created on the first visible chunk (streaming),\n// updated at most every `updateIntervalMs`, and finalized at the idle\n// state_update that ends the turn. The fold lives in the turn state the\n// platform keeps (ctx.state). An empty turn projects nothing.\n\nexport const DEFAULT_PROJECTION_UPDATE_INTERVAL_MS = 300;\n\nconst segmentSchema = z.object({ acpMessageId: z.string(), blocks: z.array(jsonObjectSchema) });\n\nexport const projectionStateSchema = z.object({\n messageId: z.string().nullable(),\n // The reply's conversation seq, so the finished reply is read back by\n // position however many messages arrived meanwhile.\n messageSeq: z.number().nullable().default(null),\n fromSeq: z.number().nullable(),\n lastSeq: z.number().nullable(),\n lastFlushAt: z.number(),\n dirty: z.boolean(),\n segments: z.array(segmentSchema),\n});\n\nexport type ProjectionStateIface = z.infer<typeof projectionStateSchema>;\n\nexport interface ProjectorConfigIface {\n updateIntervalMs?: number;\n now?: () => number;\n // The channel's turnPolicy, for routing finished replies (default steer).\n turnPolicy?: TurnPolicy;\n}\n\nconst agentChunkSchema = z.object({\n sessionUpdate: z.literal('agent_message_chunk'),\n messageId: z.string(),\n content: jsonObjectSchema,\n});\n\nconst agentMessageSchema = z.object({\n sessionUpdate: z.literal('agent_message'),\n messageId: z.string(),\n content: z.array(jsonObjectSchema).nullish(),\n});\n\nconst updateJson = (update: SessionUpdate): JsonValue => {\n const parsed = jsonObjectSchema.safeParse(JSON.parse(JSON.stringify(update)));\n return parsed.success ? parsed.data : null;\n};\n\nexport const emptyProjectionState = (): ProjectionStateIface => ({\n messageId: null,\n messageSeq: null,\n fromSeq: null,\n lastSeq: null,\n lastFlushAt: 0,\n dirty: false,\n segments: [],\n});\n\nexport const projectionStateOf = (state: JsonValue | null): ProjectionStateIface => {\n const parsed = projectionStateSchema.safeParse(state);\n return parsed.success ? parsed.data : emptyProjectionState();\n};\n\n// Plain text (no `_meta`) of a block, the only kind consecutive chunks merge into.\nconst plainText = (block: ContentBlock | undefined): string | null =>\n block == null || block._meta != null ? null : textOfBlock(block);\n\nconst appendBlock = (blocks: ContentBlock[], block: ContentBlock, separator: string): void => {\n const lastText = plainText(blocks.at(-1));\n const nextText = plainText(block);\n if (lastText != null && nextText != null) {\n blocks[blocks.length - 1] = { type: 'text', text: `${lastText}${separator}${nextText}` };\n } else {\n blocks.push(block);\n }\n};\n\n// The visible content of the turn: segments in order of first appearance,\n// separated by a blank line between text runs.\nexport const visibleContent = (state: ProjectionStateIface): ContentBlock[] => {\n const content: ContentBlock[] = [];\n for (const segment of state.segments) {\n const blocks: ContentBlock[] = [];\n for (const block of segment.blocks) {\n const parsed = contentBlockOf(block);\n if (parsed != null) appendBlock(blocks, parsed, '');\n }\n for (const block of blocks) appendBlock(content, block, '\\n\\n');\n }\n return content.flatMap((block): ContentBlock[] => {\n const text = textOfBlock(block);\n if (text == null) return [block];\n const trimmed = text.trim();\n return trimmed.length === 0 ? [] : [{ ...block, type: 'text', text: trimmed }];\n });\n};\n\nconst segmentFor = (\n state: ProjectionStateIface,\n acpMessageId: string,\n): ProjectionStateIface['segments'][number] => {\n const existing = state.segments.find((segment) => segment.acpMessageId === acpMessageId);\n if (existing != null) {\n return existing;\n }\n const segment = { acpMessageId, blocks: [] };\n state.segments.push(segment);\n return segment;\n};\n\n// Folds one ACP update into the state. Returns false for updates that are not\n// visible output.\nexport const accumulateUpdate = (state: ProjectionStateIface, update: SessionUpdate): boolean => {\n const json = updateJson(update);\n const chunk = agentChunkSchema.safeParse(json);\n if (chunk.success) {\n const block = contentBlockOf(chunk.data.content);\n if (block == null) return false;\n segmentFor(state, chunk.data.messageId).blocks.push(jsonOfContentBlock(block));\n return true;\n }\n const message = agentMessageSchema.safeParse(json);\n if (message.success) {\n // ACP v2 upsert: a concrete array replaces the message's content; omitted leaves it.\n if (message.data.content === undefined) return false;\n const blocks = (message.data.content ?? []).flatMap((block) => {\n const parsed = contentBlockOf(block);\n return parsed == null ? [] : [jsonOfContentBlock(parsed)];\n });\n segmentFor(state, message.data.messageId).blocks = blocks;\n return true;\n }\n return false;\n};\n\ninterface ConversationProjectionContextIface extends ProjectionContextIface {\n conversation: NonNullable<ProjectionContextIface['conversation']>;\n agentParticipant: NonNullable<ProjectionContextIface['agentParticipant']>;\n}\n\nexport interface ProjectorIface {\n // ChannelDefinitionIface['project'] for the messaging channels: folds visible\n // output into the turn's message and, at the turn-ending idle state_update,\n // finalizes and routes it.\n project(update: SessionUpdate, ctx: ProjectionContextIface): Promise<void>;\n}\n\nexport const createProjector = (config: ProjectorConfigIface = {}): ProjectorIface => {\n const now = config.now ?? Date.now;\n const interval = config.updateIntervalMs ?? DEFAULT_PROJECTION_UPDATE_INTERVAL_MS;\n\n const fold = async (\n update: SessionUpdate,\n ctx: ConversationProjectionContextIface,\n ): Promise<void> => {\n const state = projectionStateOf(ctx.state.get());\n if (!accumulateUpdate(state, update)) return;\n state.lastSeq = ctx.seq;\n const content = visibleContent(state);\n if (content.length === 0) {\n ctx.state.set(state);\n return;\n }\n if (state.messageId == null) {\n const message = await ctx.platform.conversations.postMessage({\n conversationId: ctx.conversation.conversationId,\n authorParticipantId: ctx.agentParticipant.id,\n kind: MessageKind.Message,\n content,\n source: { turnId: ctx.turn.id, fromSeq: ctx.seq, toSeq: ctx.seq },\n });\n state.messageId = message.messageId;\n state.messageSeq = message.seq;\n state.fromSeq = ctx.seq;\n state.lastFlushAt = now();\n state.dirty = false;\n } else if (now() - state.lastFlushAt >= interval) {\n await ctx.platform.conversations.updateMessage({\n messageId: state.messageId,\n content,\n final: false,\n });\n state.lastFlushAt = now();\n state.dirty = false;\n } else {\n state.dirty = true;\n }\n ctx.state.set(state);\n };\n\n const finish = async (ctx: ConversationProjectionContextIface): Promise<void> => {\n const state = projectionStateOf(ctx.state.get());\n if (state.messageId == null) {\n return;\n }\n await ctx.platform.conversations.updateMessage({\n messageId: state.messageId,\n content: visibleContent(state),\n final: true,\n });\n // The finished reply is routed like any message: other agents may observe\n // it or, when mentioned within the hop limit, be prompted.\n const reply =\n state.messageSeq == null\n ? null\n : (\n await ctx.platform.conversations.recentMessages({\n conversationId: ctx.conversation.conversationId,\n beforeSeq: state.messageSeq + 1,\n limit: 1,\n })\n ).find((message) => message.messageId === state.messageId);\n if (reply == null) {\n ctx.platform.log('finished reply not found for routing', {\n messageId: state.messageId,\n seq: state.messageSeq,\n });\n }\n if (reply != null) {\n if (ctx.platform.conversations.dispatchMessage == null)\n throw new Error('channel dispatch is unavailable');\n await ctx.platform.conversations.dispatchMessage({\n conversationId: reply.conversationId,\n messageId: reply.messageId,\n });\n }\n };\n\n const project: ProjectorIface['project'] = async (update, ctx) => {\n const { conversation, agentParticipant } = ctx;\n if (conversation == null || agentParticipant == null)\n throw new Error('Final rendering requires a Final conversation');\n const context = { ...ctx, conversation, agentParticipant };\n return turnEndOf(update) == null ? fold(update, context) : finish(context);\n };\n\n return { project };\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ContentBlock, SessionUpdate } from '@agentclientprotocol/sdk/experimental/v2';\nimport { textOfBlock } from '@finalai/acp-session';\nimport {\n jsonObjectSchema,\n MessageKind,\n turnEndOf,\n TurnPolicy,\n type JsonValue,\n type ProjectionContextIface,\n} from '@finalai/agent-provider';\nimport * as z from 'zod';\nimport { contentBlockOf, jsonOfContentBlock } from \"./content.mjs\";\n\n// Each ACP message ID owns a visible conversation message. The message is\n// created on its first visible chunk (streaming),\n// updated at most every `updateIntervalMs`, and finalized at the idle\n// state_update that ends the turn. The fold lives in the turn state the\n// platform keeps (ctx.state). An empty turn projects nothing.\n\nexport const DEFAULT_PROJECTION_UPDATE_INTERVAL_MS = 300;\n\nconst projectedMessageSchema = z.object({\n messageId: z.string().nullable().default(null),\n // The reply's conversation seq, so the finished reply is read back by\n // position however many messages arrived meanwhile.\n messageSeq: z.number().nullable().default(null),\n fromSeq: z.number().nullable().default(null),\n lastSeq: z.number().nullable().default(null),\n lastFlushAt: z.number().default(0),\n dirty: z.boolean().default(false),\n});\n\nconst segmentSchema = projectedMessageSchema.extend({\n acpMessageId: z.string(),\n blocks: z.array(jsonObjectSchema),\n});\n\n// Root message fields are retained for turns created by the old projector.\n// Those turns finish in their existing combined row; new turns use segment rows.\nexport const projectionStateSchema = projectedMessageSchema.extend({\n segments: z.array(segmentSchema),\n});\n\nexport type ProjectionStateIface = z.infer<typeof projectionStateSchema>;\n\nexport interface ProjectorConfigIface {\n updateIntervalMs?: number;\n now?: () => number;\n // The channel's turnPolicy, for routing finished replies (default steer).\n turnPolicy?: TurnPolicy;\n}\n\nconst agentChunkSchema = z.object({\n sessionUpdate: z.literal('agent_message_chunk'),\n messageId: z.string(),\n content: jsonObjectSchema,\n});\n\nconst agentMessageSchema = z.object({\n sessionUpdate: z.literal('agent_message'),\n messageId: z.string(),\n content: z.array(jsonObjectSchema).nullish(),\n});\n\nconst updateJson = (update: SessionUpdate): JsonValue => {\n const parsed = jsonObjectSchema.safeParse(JSON.parse(JSON.stringify(update)));\n return parsed.success ? parsed.data : null;\n};\n\nexport const emptyProjectionState = (): ProjectionStateIface => ({\n messageId: null,\n messageSeq: null,\n fromSeq: null,\n lastSeq: null,\n lastFlushAt: 0,\n dirty: false,\n segments: [],\n});\n\nexport const projectionStateOf = (state: JsonValue | null): ProjectionStateIface => {\n const parsed = projectionStateSchema.safeParse(state);\n return parsed.success ? parsed.data : emptyProjectionState();\n};\n\n// Plain text (no `_meta`) of a block, the only kind consecutive chunks merge into.\nconst plainText = (block: ContentBlock | undefined): string | null =>\n block == null || block._meta != null ? null : textOfBlock(block);\n\nconst appendBlock = (blocks: ContentBlock[], block: ContentBlock, separator: string): void => {\n const lastText = plainText(blocks.at(-1));\n const nextText = plainText(block);\n if (lastText != null && nextText != null) {\n blocks[blocks.length - 1] = { type: 'text', text: `${lastText}${separator}${nextText}` };\n } else {\n blocks.push(block);\n }\n};\n\n// The visible content of the turn: segments in order of first appearance,\n// separated by a blank line between text runs.\nexport const visibleContent = (state: Pick<ProjectionStateIface, 'segments'>): ContentBlock[] => {\n const content: ContentBlock[] = [];\n for (const segment of state.segments) {\n const blocks: ContentBlock[] = [];\n for (const block of segment.blocks) {\n const parsed = contentBlockOf(block);\n if (parsed != null) appendBlock(blocks, parsed, '');\n }\n for (const block of blocks) appendBlock(content, block, '\\n\\n');\n }\n return content.flatMap((block): ContentBlock[] => {\n const text = textOfBlock(block);\n if (text == null) return [block];\n const trimmed = text.trim();\n return trimmed.length === 0 ? [] : [{ ...block, type: 'text', text: trimmed }];\n });\n};\n\nconst segmentFor = (\n state: ProjectionStateIface,\n acpMessageId: string,\n): ProjectionStateIface['segments'][number] => {\n const existing = state.segments.find((segment) => segment.acpMessageId === acpMessageId);\n if (existing != null) {\n return existing;\n }\n const segment = segmentSchema.parse({ acpMessageId, blocks: [] });\n state.segments.push(segment);\n return segment;\n};\n\n// Folds one ACP update into the state and returns the affected segment, or null\n// for updates that are not visible output.\nexport const accumulateUpdate = (\n state: ProjectionStateIface,\n update: SessionUpdate,\n): ProjectionStateIface['segments'][number] | null => {\n const json = updateJson(update);\n const chunk = agentChunkSchema.safeParse(json);\n if (chunk.success) {\n const block = contentBlockOf(chunk.data.content);\n if (block == null) return null;\n const segment = segmentFor(state, chunk.data.messageId);\n segment.blocks.push(jsonOfContentBlock(block));\n return segment;\n }\n const message = agentMessageSchema.safeParse(json);\n if (message.success) {\n // ACP v2 upsert: a concrete array replaces the message's content; omitted leaves it.\n if (message.data.content === undefined) return null;\n const blocks = (message.data.content ?? []).flatMap((block) => {\n const parsed = contentBlockOf(block);\n return parsed == null ? [] : [jsonOfContentBlock(parsed)];\n });\n const segment = segmentFor(state, message.data.messageId);\n segment.blocks = blocks;\n return segment;\n }\n return null;\n};\n\ninterface ConversationProjectionContextIface extends ProjectionContextIface {\n conversation: NonNullable<ProjectionContextIface['conversation']>;\n agentParticipant: NonNullable<ProjectionContextIface['agentParticipant']>;\n}\n\nexport interface ProjectorIface {\n // ChannelDefinitionIface['project'] for the messaging channels: folds visible\n // output into separate messages and, at the turn-ending idle state_update,\n // finalizes and routes them in order of first appearance.\n project(update: SessionUpdate, ctx: ProjectionContextIface): Promise<void>;\n}\n\nexport const createProjector = (config: ProjectorConfigIface = {}): ProjectorIface => {\n const now = config.now ?? Date.now;\n const interval = config.updateIntervalMs ?? DEFAULT_PROJECTION_UPDATE_INTERVAL_MS;\n\n const fold = async (\n update: SessionUpdate,\n ctx: ConversationProjectionContextIface,\n ): Promise<void> => {\n const state = projectionStateOf(ctx.state.get());\n const segment = accumulateUpdate(state, update);\n if (segment == null) return;\n const target = state.messageId == null ? segment : state;\n target.lastSeq = ctx.seq;\n const content = visibleContent({ segments: target === state ? state.segments : [segment] });\n if (content.length === 0 && target.messageId == null) {\n ctx.state.set(state);\n return;\n }\n if (target.messageId == null) {\n const message = await ctx.platform.conversations.postMessage({\n conversationId: ctx.conversation.conversationId,\n authorParticipantId: ctx.agentParticipant.id,\n kind: MessageKind.Message,\n content,\n source: { turnId: ctx.turn.id, fromSeq: ctx.seq, toSeq: ctx.seq },\n });\n target.messageId = message.messageId;\n target.messageSeq = message.seq;\n target.fromSeq = ctx.seq;\n target.lastFlushAt = now();\n target.dirty = false;\n } else if (now() - target.lastFlushAt >= interval) {\n await ctx.platform.conversations.updateMessage({\n messageId: target.messageId,\n content,\n final: false,\n });\n target.lastFlushAt = now();\n target.dirty = false;\n } else {\n target.dirty = true;\n }\n ctx.state.set(state);\n };\n\n const finish = async (ctx: ConversationProjectionContextIface): Promise<void> => {\n const state = projectionStateOf(ctx.state.get());\n if (state.messageId != null) {\n await finishMessage(ctx, state, visibleContent(state));\n return;\n }\n for (const segment of state.segments) {\n await finishMessage(ctx, segment, visibleContent({ segments: [segment] }));\n }\n };\n\n const finishMessage = async (\n ctx: ConversationProjectionContextIface,\n state: z.infer<typeof projectedMessageSchema>,\n content: ContentBlock[],\n ): Promise<void> => {\n if (state.messageId == null) {\n return;\n }\n await ctx.platform.conversations.updateMessage({\n messageId: state.messageId,\n content,\n final: true,\n });\n // The finished reply is routed like any message: other agents may observe\n // it or, when mentioned within the hop limit, be prompted.\n const reply =\n state.messageSeq == null\n ? null\n : (\n await ctx.platform.conversations.recentMessages({\n conversationId: ctx.conversation.conversationId,\n beforeSeq: state.messageSeq + 1,\n limit: 1,\n })\n ).find((message) => message.messageId === state.messageId);\n if (reply == null) {\n ctx.platform.log('finished reply not found for routing', {\n messageId: state.messageId,\n seq: state.messageSeq,\n });\n }\n if (reply != null) {\n if (ctx.platform.conversations.dispatchMessage == null)\n throw new Error('channel dispatch is unavailable');\n await ctx.platform.conversations.dispatchMessage({\n conversationId: reply.conversationId,\n messageId: reply.messageId,\n });\n }\n };\n\n const project: ProjectorIface['project'] = async (update, ctx) => {\n const { conversation, agentParticipant } = ctx;\n if (conversation == null || agentParticipant == null)\n throw new Error('Final rendering requires a Final conversation');\n const context = { ...ctx, conversation, agentParticipant };\n return turnEndOf(update) == null ? fold(update, context) : finish(context);\n };\n\n return { project };\n};\n"],
5
+ "mappings": "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",
6
6
  "names": ["segment"]
7
7
  }
@@ -10,8 +10,9 @@ export declare const computerWorkspaceName: (options: {
10
10
  target: string;
11
11
  conversationWorkspace: boolean;
12
12
  }) => Promise<string>;
13
- export type { ComputersApiConfigIface, ComputersApiCredentialIface } from "./computers-api.mjs";
14
- export { COMPUTER_SANDBOX_SPECIFIER, COMPUTERS_API_KEY_SECRET, COMPUTERS_API_TEAM_SECRET, } from "./environment.mjs";
13
+ export type { ComputersApiConfigIface } from "./computers-api.mjs";
14
+ export type { FinalPlatformAccessIface } from '@finalai/agent-provider/sandbox';
15
+ export { COMPUTER_SANDBOX_SPECIFIER } from "./environment.mjs";
15
16
  export { computeComputerArtifactKey } from "./guide.mjs";
16
17
  export type { ComputerGuideVersionIface } from "./guide.mjs";
17
18
  export type { RepositoryCloneSourceIface } from "./workspace-setup.mjs";
@@ -19,7 +20,7 @@ export interface ComputerSandboxLogIface {
19
20
  (message: string, fields?: Record<string, string | number | boolean | null>): void;
20
21
  }
21
22
  export interface ComputerSandboxProviderConfigIface {
22
- computersApi: ComputersApiConfigIface;
23
+ computersApi?: ComputersApiConfigIface;
23
24
  networking?: SandboxNetworkingExtensionIface;
24
25
  registryCredentialId?: string;
25
26
  defaultImage?: string;
@@ -46,7 +46,6 @@ import { computerEditor } from "./computer-editor.mjs";
46
46
  import { readComputerSnapshot } from "./computer-snapshots.mjs";
47
47
  import {
48
48
  createComputersApiClient,
49
- createComputersApiCredentials,
50
49
  isComputersApiError,
51
50
  isComputersApiStatus
52
51
  } from "./computers-api.mjs";
@@ -75,11 +74,7 @@ import {
75
74
  WORKSPACE_ROOT
76
75
  } from "./workspace-setup.mjs";
77
76
  const computerWorkspaceName = async (options) => options.conversationWorkspace && options.conversationId != null ? `ac-${(await sha256Hex(`${options.conversationId}:${options.target}`)).slice(0, 24)}` : `ab-${compactId(options.bindingId)}`;
78
- import {
79
- COMPUTER_SANDBOX_SPECIFIER as COMPUTER_SANDBOX_SPECIFIER2,
80
- COMPUTERS_API_KEY_SECRET,
81
- COMPUTERS_API_TEAM_SECRET
82
- } from "./environment.mjs";
77
+ import { COMPUTER_SANDBOX_SPECIFIER as COMPUTER_SANDBOX_SPECIFIER2 } from "./environment.mjs";
83
78
  import { computeComputerArtifactKey as computeComputerArtifactKey2 } from "./guide.mjs";
84
79
  const computerArtifactSchema = sandboxSeedArtifactSchema.extend({
85
80
  version: z.literal(1),
@@ -180,9 +175,8 @@ const createComputerSandboxProvider = (config) => {
180
175
  const now = config.now ?? (() => Date.now());
181
176
  const sleep = config.sleep ?? defaultSleep;
182
177
  const defaultImage = config.defaultImage ?? DEFAULT_COMPUTER_IMAGE;
183
- const credentials = createComputersApiCredentials(config.computersApi, now);
184
178
  const runningWaitMs = config.runningWaitMs ?? 5 * 6e4;
185
- const clientFor = (organizationId) => createComputersApiClient({ config: config.computersApi, credentials, organizationId });
179
+ const clientFor = (credentials) => createComputersApiClient({ config: config.computersApi ?? {}, credentials });
186
180
  const registerImage = (client, reference) => client.registerImage(
187
181
  config.registryCredentialId == null ? { reference } : { reference, registryCredentialId: config.registryCredentialId }
188
182
  );
@@ -627,6 +621,7 @@ ${commandHelp}`}`,
627
621
  platformEgressHosts: args.ctx.platformEgressHosts,
628
622
  platformNetworkPolicy: args.ctx.platformNetworkPolicy,
629
623
  organizationId: args.ctx.session.organizationId,
624
+ credentials: args.ctx.credentials,
630
625
  actingUser: args.ctx.actingUser,
631
626
  bindings: args.ctx.bindings
632
627
  },
@@ -664,6 +659,7 @@ ${commandHelp}`}`,
664
659
  platformEgressHosts: args.ctx.platformEgressHosts,
665
660
  platformNetworkPolicy: args.ctx.platformNetworkPolicy,
666
661
  organizationId: args.ctx.session.organizationId,
662
+ credentials: args.ctx.credentials,
667
663
  actingUser: args.ctx.actingUser,
668
664
  bindings: args.ctx.bindings
669
665
  },
@@ -678,6 +674,7 @@ ${commandHelp}`}`,
678
674
  ctx: {
679
675
  platformEgressHosts: ctx.platformEgressHosts,
680
676
  organizationId: ctx.session.organizationId,
677
+ credentials: ctx.credentials,
681
678
  actingUser: ctx.actingUser,
682
679
  bindings: ctx.bindings
683
680
  },
@@ -715,7 +712,7 @@ ${commandHelp}`}`,
715
712
  const results = await Promise.allSettled([
716
713
  (async () => {
717
714
  if (managed && args.fence) {
718
- const client = clientFor(args.ctx.session.organizationId);
715
+ const client = clientFor(args.ctx.credentials);
719
716
  let mayFence = args.policySaved || savedPolicyWorkspaces.has(args.ctx.session.organizationId + ":" + workspaceId);
720
717
  try {
721
718
  if (config.networking != null) {
@@ -762,6 +759,7 @@ ${commandHelp}`}`,
762
759
  await config.networking.suspend({
763
760
  ctx: {
764
761
  organizationId: args.ctx.session.organizationId,
762
+ credentials: args.ctx.credentials,
765
763
  actingUser: args.ctx.actingUser,
766
764
  bindings: args.ctx.bindings
767
765
  },
@@ -832,7 +830,7 @@ ${commandHelp}`}`,
832
830
  }
833
831
  };
834
832
  const build = async (ctx, spec, key) => {
835
- const client = clientFor(ctx.organizationId);
833
+ const client = clientFor(ctx.credentials);
836
834
  const diskGb = config.machine?.diskGb ?? null;
837
835
  const imageVersionId = await readyImageVersion(client, {
838
836
  image: spec.image,
@@ -965,7 +963,7 @@ ${step.run}`,
965
963
  });
966
964
  };
967
965
  const awaitRunning = (ctx, rawState, options2) => awaitComputerRunning(
968
- clientFor(ctx.session.organizationId),
966
+ clientFor(ctx.credentials),
969
967
  computerStateSchema.pick({ computerId: true }).parse(rawState).computerId,
970
968
  options2.timeoutMs
971
969
  );
@@ -974,10 +972,7 @@ ${step.run}`,
974
972
  const state = computerStateSchema.parse(rawState);
975
973
  if (request.operation === ProcessInspectionOperation.Stop && state.access === SandboxAccess.Read)
976
974
  throw new Error("stopping processes requires execute access");
977
- const result = await clientFor(ctx.organizationId).inspectProcesses(
978
- state.computerId,
979
- request
980
- );
975
+ const result = await clientFor(ctx.credentials).inspectProcesses(state.computerId, request);
981
976
  return request.operation === ProcessInspectionOperation.Available && "available" in result ? { ...result, canStop: state.access !== SandboxAccess.Read } : result;
982
977
  },
983
978
  editor: async (ctx, rawState, request) => {
@@ -986,7 +981,7 @@ ${step.run}`,
986
981
  if (request.operation === "available") return { available: false };
987
982
  throw new Error("editor requires write access");
988
983
  }
989
- const client = clientFor(ctx.organizationId);
984
+ const client = clientFor(ctx.credentials);
990
985
  const open = (expiresAtMs) => computerEditor({
991
986
  client,
992
987
  computerId: state.computerId,
@@ -1050,7 +1045,7 @@ ${step.run}`,
1050
1045
  readSnapshot: async (ctx, rawState, request) => {
1051
1046
  const state = computerStateSchema.parse(rawState);
1052
1047
  return readComputerSnapshot({
1053
- client: clientFor(ctx.organizationId),
1048
+ client: clientFor(ctx.credentials),
1054
1049
  computerId: state.computerId,
1055
1050
  request
1056
1051
  });
@@ -1073,13 +1068,10 @@ ${step.run}`,
1073
1068
  if (prepared?.key === baseKey && prepared.providerRef === formatProviderRef(packageRef(COMPUTER_SANDBOX_SPECIFIER))) {
1074
1069
  const cached2 = computerArtifactSchema.safeParse(prepared.artifact);
1075
1070
  if (cached2.success && cached2.data.key === baseKey && cached2.data.base.kind === "captured") {
1076
- const base = await clientFor(ctx.organizationId).getBase(cached2.data.base.baseId);
1071
+ const base = await clientFor(ctx.credentials).getBase(cached2.data.base.baseId);
1077
1072
  if (base?.state === ComputerBaseState.Ready) {
1078
1073
  if (!base.retained) {
1079
- const retained = await clientFor(ctx.organizationId).setBaseRetention(
1080
- base.id,
1081
- true
1082
- );
1074
+ const retained = await clientFor(ctx.credentials).setBaseRetention(base.id, true);
1083
1075
  if (!retained.retained)
1084
1076
  throw computerSandboxError(
1085
1077
  "build_failed",
@@ -1092,13 +1084,10 @@ ${step.run}`,
1092
1084
  }
1093
1085
  const cached = computerArtifactSchema.safeParse(await ctx.artifacts?.get(baseKey));
1094
1086
  if (cached.success && cached.data.key === baseKey && cached.data.base.kind === "captured") {
1095
- const base = await clientFor(ctx.organizationId).getBase(cached.data.base.baseId);
1087
+ const base = await clientFor(ctx.credentials).getBase(cached.data.base.baseId);
1096
1088
  if (base?.state === ComputerBaseState.Ready) {
1097
1089
  if (!base.retained) {
1098
- const retained = await clientFor(ctx.organizationId).setBaseRetention(
1099
- base.id,
1100
- true
1101
- );
1090
+ const retained = await clientFor(ctx.credentials).setBaseRetention(base.id, true);
1102
1091
  if (!retained.retained)
1103
1092
  throw computerSandboxError(
1104
1093
  "build_failed",
@@ -1112,7 +1101,7 @@ ${step.run}`,
1112
1101
  await ctx.artifacts?.put(baseKey, built);
1113
1102
  return { ...built, key, ...seedArtifact };
1114
1103
  }
1115
- const imageVersionId = spec.image.startsWith(LINEAGE_PREFIX) ? null : (await registerImage(clientFor(ctx.organizationId), spec.image)).id;
1104
+ const imageVersionId = spec.image.startsWith(LINEAGE_PREFIX) ? null : (await registerImage(clientFor(ctx.credentials), spec.image)).id;
1116
1105
  ctx.log?.(`computer base ${key} is image ${spec.image}`);
1117
1106
  return {
1118
1107
  version: 1,
@@ -1146,7 +1135,7 @@ ${step.run}`,
1146
1135
  );
1147
1136
  }
1148
1137
  const env = platformProcessEnvironment(ctx, await loadVariables(ctx, spec));
1149
- const client = clientFor(ctx.session.organizationId);
1138
+ const client = clientFor(ctx.credentials);
1150
1139
  const workspace = spec.parameters.workspace;
1151
1140
  const name = await computerWorkspaceName({
1152
1141
  ...ctx.session,
@@ -1366,7 +1355,7 @@ ${step.run}`,
1366
1355
  ctx = { ...ctx, session: { ...ctx.session, workspaceId: state.workspaceId } };
1367
1356
  if (state.networkPolicySaved === true && state.workspaceId != null && ctx.session.workspaceId !== state.workspaceId)
1368
1357
  throw new Error("Passive workspace identity does not match retained provider state");
1369
- const client = clientFor(ctx.session.organizationId);
1358
+ const client = clientFor(ctx.credentials);
1370
1359
  const info = await client.getComputer(state.computerId);
1371
1360
  if (info == null || info.state !== ComputerState.Running)
1372
1361
  throw computerSandboxError(
@@ -1409,7 +1398,7 @@ ${step.run}`,
1409
1398
  })
1410
1399
  }
1411
1400
  };
1412
- const client = clientFor(ctx.session.organizationId);
1401
+ const client = clientFor(ctx.credentials);
1413
1402
  const workspaceId = sandboxWorkspaceId({
1414
1403
  session: ctx.session,
1415
1404
  workspace: sandboxWorkspaceOf[state.workspace]
@@ -1579,7 +1568,7 @@ ${step.run}`,
1579
1568
  failures.push(cause);
1580
1569
  }
1581
1570
  try {
1582
- const client = clientFor(ctx.session.organizationId);
1571
+ const client = clientFor(ctx.credentials);
1583
1572
  let current;
1584
1573
  try {
1585
1574
  current = await client.getComputer(state.computerId);
@@ -1621,7 +1610,7 @@ ${step.run}`,
1621
1610
  },
1622
1611
  onSessionEnd: async (ctx, _artifact, rawState, { reason }) => {
1623
1612
  const state = computerStateSchema.parse(rawState);
1624
- const client = clientFor(ctx.session.organizationId);
1613
+ const client = clientFor(ctx.credentials);
1625
1614
  const finish = async () => {
1626
1615
  const action = state.workspace === ComputerWorkspacePolicy.Conversation ? "stop" : "delete";
1627
1616
  log("computer_sandbox.session_end", { computerId: state.computerId, reason, action });
@@ -1657,8 +1646,6 @@ ${step.run}`,
1657
1646
  });
1658
1647
  };
1659
1648
  export {
1660
- COMPUTERS_API_KEY_SECRET,
1661
- COMPUTERS_API_TEAM_SECRET,
1662
1649
  COMPUTER_SANDBOX_SPECIFIER2 as COMPUTER_SANDBOX_SPECIFIER,
1663
1650
  computeComputerArtifactKey2 as computeComputerArtifactKey,
1664
1651
  computerWorkspaceName,