@finalai/final-agent-provider 0.10.0 → 0.11.0

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 (161) hide show
  1. package/HOSTING.md +334 -245
  2. package/README.md +126 -40
  3. package/WORKSPACE-EGRESS.md +11 -11
  4. package/dist/modules/catalog.d.mts +9 -0
  5. package/dist/modules/catalog.mjs +21 -0
  6. package/dist/modules/catalog.mjs.map +7 -0
  7. package/dist/modules/channels/projection.mjs +15 -8
  8. package/dist/modules/channels/projection.mjs.map +2 -2
  9. package/dist/modules/harnesses/acp-agent.d.mts +4 -0
  10. package/dist/modules/harnesses/acp-agent.mjs +35 -0
  11. package/dist/modules/harnesses/acp-agent.mjs.map +7 -0
  12. package/dist/modules/harnesses/adapters.d.mts +7 -0
  13. package/dist/modules/harnesses/adapters.mjs +74 -0
  14. package/dist/modules/harnesses/adapters.mjs.map +7 -0
  15. package/dist/modules/harnesses/claude-code.d.mts +14 -0
  16. package/dist/modules/harnesses/claude-code.mjs +79 -0
  17. package/dist/modules/harnesses/claude-code.mjs.map +7 -0
  18. package/dist/modules/harnesses/codex.d.mts +12 -0
  19. package/dist/modules/harnesses/codex.mjs +90 -0
  20. package/dist/modules/harnesses/codex.mjs.map +7 -0
  21. package/dist/modules/harnesses/config-fields.d.mts +98 -57
  22. package/dist/modules/harnesses/config-fields.mjs +58 -35
  23. package/dist/modules/harnesses/config-fields.mjs.map +3 -3
  24. package/dist/modules/harnesses/definitions.d.mts +3 -0
  25. package/dist/modules/harnesses/definitions.mjs +21 -0
  26. package/dist/modules/harnesses/definitions.mjs.map +7 -0
  27. package/dist/modules/harnesses/fidelity.d.mts +1 -1
  28. package/dist/modules/harnesses/fidelity.mjs +19 -16
  29. package/dist/modules/harnesses/fidelity.mjs.map +2 -2
  30. package/dist/modules/harnesses/final.d.mts +12 -0
  31. package/dist/modules/harnesses/final.mjs +70 -0
  32. package/dist/modules/harnesses/final.mjs.map +7 -0
  33. package/dist/modules/harnesses/index.d.mts +13 -12
  34. package/dist/modules/harnesses/index.mjs +86 -45
  35. package/dist/modules/harnesses/index.mjs.map +3 -3
  36. package/dist/modules/harnesses/names.d.mts +43 -0
  37. package/dist/modules/harnesses/names.mjs +59 -0
  38. package/dist/modules/harnesses/names.mjs.map +7 -0
  39. package/dist/modules/harnesses/nanocode-claude.d.mts +18 -0
  40. package/dist/modules/harnesses/nanocode-claude.mjs +101 -0
  41. package/dist/modules/harnesses/nanocode-claude.mjs.map +7 -0
  42. package/dist/modules/harnesses/nanocode-codex.d.mts +13 -0
  43. package/dist/modules/harnesses/nanocode-codex.mjs +64 -0
  44. package/dist/modules/harnesses/nanocode-codex.mjs.map +7 -0
  45. package/dist/modules/harnesses/pi.d.mts +12 -0
  46. package/dist/modules/harnesses/pi.mjs +71 -0
  47. package/dist/modules/harnesses/pi.mjs.map +7 -0
  48. package/dist/modules/integrations.d.mts +9 -0
  49. package/dist/modules/integrations.mjs +29 -0
  50. package/dist/modules/integrations.mjs.map +7 -0
  51. package/dist/modules/provider/general.bundle.d.mts +1 -0
  52. package/dist/modules/provider/general.bundle.mjs +5 -0
  53. package/dist/modules/provider/general.bundle.mjs.map +7 -0
  54. package/dist/modules/provider/general.d.mts +11 -0
  55. package/dist/modules/provider/general.mjs +130 -0
  56. package/dist/modules/provider/general.mjs.map +7 -0
  57. package/dist/modules/provider/index.d.mts +3 -0
  58. package/dist/modules/provider/index.mjs +13 -0
  59. package/dist/modules/provider/index.mjs.map +7 -0
  60. package/dist/modules/sandboxes/access.d.mts +34 -0
  61. package/dist/modules/sandboxes/access.mjs +15 -0
  62. package/dist/modules/sandboxes/access.mjs.map +7 -0
  63. package/dist/modules/sandboxes/computer.d.mts +24 -51
  64. package/dist/modules/sandboxes/computer.mjs +523 -888
  65. package/dist/modules/sandboxes/computer.mjs.map +3 -3
  66. package/dist/modules/sandboxes/computers-api.d.mts +3 -2
  67. package/dist/modules/sandboxes/computers-api.mjs +2 -4
  68. package/dist/modules/sandboxes/computers-api.mjs.map +2 -2
  69. package/dist/modules/sandboxes/device-api.d.mts +4 -3
  70. package/dist/modules/sandboxes/device-api.mjs +2 -4
  71. package/dist/modules/sandboxes/device-api.mjs.map +2 -2
  72. package/dist/modules/sandboxes/device.d.mts +6 -6
  73. package/dist/modules/sandboxes/device.mjs +126 -121
  74. package/dist/modules/sandboxes/device.mjs.map +3 -3
  75. package/dist/modules/sandboxes/egress.d.mts +10 -9
  76. package/dist/modules/sandboxes/egress.mjs +73 -56
  77. package/dist/modules/sandboxes/egress.mjs.map +2 -2
  78. package/dist/modules/sandboxes/environment.d.mts +35 -269
  79. package/dist/modules/sandboxes/environment.mjs +27 -104
  80. package/dist/modules/sandboxes/environment.mjs.map +3 -3
  81. package/dist/modules/sandboxes/index.d.mts +9 -69
  82. package/dist/modules/sandboxes/index.mjs +35 -95
  83. package/dist/modules/sandboxes/index.mjs.map +3 -3
  84. package/dist/modules/sandboxes/local.d.mts +28 -0
  85. package/dist/modules/sandboxes/local.mjs +499 -0
  86. package/dist/modules/sandboxes/local.mjs.map +7 -0
  87. package/dist/modules/sandboxes/session.d.mts +0 -3
  88. package/dist/modules/sandboxes/session.mjs +2 -22
  89. package/dist/modules/sandboxes/session.mjs.map +2 -2
  90. package/dist/modules/sandboxes/testing/fake-machine.mjs +0 -28
  91. package/dist/modules/sandboxes/testing/fake-machine.mjs.map +2 -2
  92. package/package.json +76 -104
  93. package/dist/modules/connections.d.mts +0 -11
  94. package/dist/modules/connections.mjs +0 -16
  95. package/dist/modules/connections.mjs.map +0 -7
  96. package/dist/modules/harnesses/acp-describe.d.mts +0 -20
  97. package/dist/modules/harnesses/acp-describe.mjs +0 -110
  98. package/dist/modules/harnesses/acp-describe.mjs.map +0 -7
  99. package/dist/modules/harnesses/local-stdio.d.mts +0 -47
  100. package/dist/modules/harnesses/local-stdio.mjs +0 -41
  101. package/dist/modules/harnesses/local-stdio.mjs.map +0 -7
  102. package/dist/modules/harnesses/remote-acp.d.mts +0 -76
  103. package/dist/modules/harnesses/remote-acp.mjs +0 -83
  104. package/dist/modules/harnesses/remote-acp.mjs.map +0 -7
  105. package/dist/modules/harnesses/remote-claude-code.d.mts +0 -88
  106. package/dist/modules/harnesses/remote-claude-code.mjs +0 -113
  107. package/dist/modules/harnesses/remote-claude-code.mjs.map +0 -7
  108. package/dist/modules/harnesses/remote-client-tls.d.mts +0 -35
  109. package/dist/modules/harnesses/remote-client-tls.mjs +0 -37
  110. package/dist/modules/harnesses/remote-client-tls.mjs.map +0 -7
  111. package/dist/modules/harnesses/remote-codex.d.mts +0 -98
  112. package/dist/modules/harnesses/remote-codex.mjs +0 -128
  113. package/dist/modules/harnesses/remote-codex.mjs.map +0 -7
  114. package/dist/modules/harnesses/remote-pi.d.mts +0 -78
  115. package/dist/modules/harnesses/remote-pi.mjs +0 -104
  116. package/dist/modules/harnesses/remote-pi.mjs.map +0 -7
  117. package/dist/modules/harnesses/remote-url.d.mts +0 -5
  118. package/dist/modules/harnesses/remote-url.mjs +0 -19
  119. package/dist/modules/harnesses/remote-url.mjs.map +0 -7
  120. package/dist/modules/harnesses/serverless-final.d.mts +0 -63
  121. package/dist/modules/harnesses/serverless-final.mjs +0 -88
  122. package/dist/modules/harnesses/serverless-final.mjs.map +0 -7
  123. package/dist/modules/harnesses/serverless-nanocode-claude.d.mts +0 -64
  124. package/dist/modules/harnesses/serverless-nanocode-claude.mjs +0 -105
  125. package/dist/modules/harnesses/serverless-nanocode-claude.mjs.map +0 -7
  126. package/dist/modules/harnesses/serverless-nanocode-codex.d.mts +0 -66
  127. package/dist/modules/harnesses/serverless-nanocode-codex.mjs +0 -113
  128. package/dist/modules/harnesses/serverless-nanocode-codex.mjs.map +0 -7
  129. package/dist/modules/harnesses/serverless-pi.d.mts +0 -64
  130. package/dist/modules/harnesses/serverless-pi.mjs +0 -98
  131. package/dist/modules/harnesses/serverless-pi.mjs.map +0 -7
  132. package/dist/modules/runtime/index.d.mts +0 -7
  133. package/dist/modules/runtime/index.mjs +0 -18
  134. package/dist/modules/runtime/index.mjs.map +0 -7
  135. package/dist/modules/runtime/local-stdio.d.mts +0 -10
  136. package/dist/modules/runtime/local-stdio.mjs +0 -106
  137. package/dist/modules/runtime/local-stdio.mjs.map +0 -7
  138. package/dist/modules/runtime/remote-acp.d.mts +0 -16
  139. package/dist/modules/runtime/remote-acp.mjs +0 -152
  140. package/dist/modules/runtime/remote-acp.mjs.map +0 -7
  141. package/dist/modules/runtime/remote-claude-code.d.mts +0 -5
  142. package/dist/modules/runtime/remote-claude-code.mjs +0 -47
  143. package/dist/modules/runtime/remote-claude-code.mjs.map +0 -7
  144. package/dist/modules/runtime/remote-codex.d.mts +0 -5
  145. package/dist/modules/runtime/remote-codex.mjs +0 -47
  146. package/dist/modules/runtime/remote-codex.mjs.map +0 -7
  147. package/dist/modules/runtime/remote-host.d.mts +0 -127
  148. package/dist/modules/runtime/remote-host.mjs +0 -387
  149. package/dist/modules/runtime/remote-host.mjs.map +0 -7
  150. package/dist/modules/runtime/remote-pi.d.mts +0 -5
  151. package/dist/modules/runtime/remote-pi.mjs +0 -44
  152. package/dist/modules/runtime/remote-pi.mjs.map +0 -7
  153. package/dist/modules/sandboxes/guide.d.mts +0 -35
  154. package/dist/modules/sandboxes/guide.mjs +0 -119
  155. package/dist/modules/sandboxes/guide.mjs.map +0 -7
  156. package/dist/modules/sandboxes/workspace-setup.d.mts +0 -42
  157. package/dist/modules/sandboxes/workspace-setup.mjs +0 -242
  158. package/dist/modules/sandboxes/workspace-setup.mjs.map +0 -7
  159. package/dist/modules/testing/harness.d.mts +0 -9
  160. package/dist/modules/testing/harness.mjs +0 -123
  161. package/dist/modules/testing/harness.mjs.map +0 -7
package/README.md CHANGED
@@ -1,41 +1,130 @@
1
1
  # @finalai/final-agent-provider
2
2
 
3
- Provider helpers for harnesses, computer and device sandboxes, the Final room, and
4
- external channels. Import helpers from `/harnesses`, `/sandboxes`, or `/channels/final`.
5
- `/connections` provides `defineFinalConnection` for session-scoped Final MCP access.
6
-
7
- Optional Node ACP adapters are exposed under `/runtime/*`. Sandbox adapters use
8
- the public Computers and Device APIs; the Computers transport is shared with
9
- `@finalai/computers`. Runtime configuration and credentials are injected.
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
-
15
- Authoring helpers produce manifest bindings. Select the matching `/runtime/*`
16
- adapter when connecting your own CLI or ACP endpoint.
17
-
18
- `defineFinalConnection()` requests the host's `final` binding at session start,
19
- using the generic `defineHostedConnection` implementation. Its session grant still
20
- goes through the usual credential broker. Existing calls with an explicit `url`
21
- remain static. Hosts without a binding reader cannot run URL-free definitions;
22
- see the repository's [hosting guide](HOSTING.md) for wiring requirements.
23
-
24
- ## Hosted model references
25
-
26
- The serverless Final, Pi, Codex and Claude helpers accept model strings, including provider-qualified references such as `openrouter/deepseek/deepseek-v4.1-flash`. Existing short model IDs remain valid aliases when the installed platform catalog supports them. The SDK checks printable ASCII reference syntax (up to 200 characters); the installed platform selects defaults, validates availability and supplies model picker suggestions. Omitting `model` inherits the platform default. Host integrators must now handle `serverless*ConfigSchema.parse(...).model` being undefined and resolve the default before dispatch. Local compilation does not fetch a model catalog.
27
-
28
- Adding a model on an already-supported provider API requires a platform catalog update, not an SDK release. Native nanocode models must use an engine policy the platform supports. New provider protocols still require an integration.
29
-
30
- The `modelZod(ids)` and `modelJson(ids, defaultId)` helpers retain explicit restrictions for third-party harness authors who choose to supply them.
31
-
32
- SDK 0.2.0 removes `@finalai/agents/models` and `@finalai/final-agent-provider/harnesses/models`, including their model arrays, defaults, model-ID unions and pricing helpers. Use string model references and the installed platform’s harness description for current candidates and defaults. Pricing belongs to the hosting platform. The protocol-level `ModelVendor` enum remains available from `@finalai/final-agent-provider/harnesses`.
3
+ Final's harness and ACP adapter definitions, the general harness provider,
4
+ sandbox providers and MCP gateway integration, as a caller provides them to an
5
+ agent, plus the Final room surface and external channels.
6
+
7
+ ## Harness settings
8
+
9
+ `/harnesses` exports one settings builder per Final harness. Each returns plain
10
+ harness settings, `{ harness, configuration, environment? }`, the wire form a
11
+ caller passes as `provide.harness`:
12
+
13
+ ```ts
14
+ import {
15
+ apiKey,
16
+ finalCredits,
17
+ serverlessNanocodeClaude,
18
+ subscription,
19
+ } from '@finalai/final-agent-provider/harnesses';
20
+
21
+ serverlessNanocodeClaude({
22
+ model: 'claude-opus-5-5',
23
+ inference: [subscription(), apiKey('ANTHROPIC_API_KEY'), finalCredits()],
24
+ });
25
+ ```
26
+
27
+ - `FinalHarness` names the definitions as settings reach them, qualified by
28
+ Final's publisher handle (`final/pi`); `FinalHarnessName` is the name each is
29
+ published under. `final`, `pi`, `nanocode-claude` and
30
+ `nanocode-codex` run as worker programs (`serverlessFinal`, `serverlessPi`,
31
+ `serverlessNanocodeClaude`, `serverlessNanocodeCodex`); `claude-code` and
32
+ `codex` run as processes on a machine (`claudeCode`, `codex`); `acp-agent` is
33
+ any agent that already speaks ACP v2 with Final's ACP extensions, reached at its
34
+ URL with no adapter (`acpAgent({ url, hop })`).
35
+ - `inference` is one source or an ordered list (`subscription`, `apiKey`,
36
+ `finalCredits`, `harnessLogin`); a single source is stored as a one-item list.
37
+ - Worker harnesses require `model`, an open reference the harness validates
38
+ (`openrouter/deepseek/deepseek-v4.1-flash` included). There is no default model.
39
+ - `permissionMode` is `ask`, `review` or `auto`, within the agent's ceiling; each
40
+ harness's adapter maps it to its program's own mode.
41
+ - `environment` is the sandbox the harness runs in, or `atUrl({ url, hop })` for
42
+ an ACP endpoint already running it (`hop` names the project secret holding its
43
+ bearer token). Absent, a process harness runs in the sandbox provided for
44
+ `shell`, and the client gives a worker harness a sandbox created from
45
+ `workers()`, owned by the session or conversation.
46
+
47
+ Instructions, skills and tool policy come from the agent's document, never from
48
+ harness settings.
49
+
50
+ ## Definitions and the general provider
51
+
52
+ Each harness is a definition (`FINAL_HARNESS_DEFINITIONS`): its configuration
53
+ schema, the features it supports, the lowest permission it can be held to, the
54
+ inference it can use, what it uses natively where it runs, its default ACP
55
+ adapter, and how it runs (a worker program, or a process command derived from
56
+ its configuration). Each ACP adapter (`FINAL_ACP_ADAPTERS`) maps one harness
57
+ interface to ACP v2 and Final's ACP extensions:
58
+
59
+ | Adapter | Adapts | Runs as |
60
+ | ----------------- | ----------------------------------- | -------------------------------------------------------------- |
61
+ | `pi-acp` | `pi`, `final` | worker program `pi` |
62
+ | `nanocode-acp` | `nanocode-claude`, `nanocode-codex` | worker program `nanocode` |
63
+ | `claude-code-acp` | `claude-code` | `npx -y @finalai/acp-adapter-claude-code -- <harness command>` |
64
+ | `codex-acp` | `codex` | `npx -y @finalai/acp-adapter-codex -- <harness command>` |
65
+
66
+ Pi has no process adapter, so it runs only as a worker program or at a URL.
67
+ `acp-agent` speaks ACP v2 itself and has no run entry: it runs only at a URL.
68
+
69
+ `validateHarnessConfiguration` (from `@finalai/agent-provider`) checks harness
70
+ settings' configuration against a definition: its configuration schema with the
71
+ shared `final://schemas/*` fragments, its lowest permission and the inference it
72
+ can use.
73
+
74
+ `/provider` is the general harness provider, one self-contained module built
75
+ with `defineHarnessProvider` on the public provider contract. It starts
76
+ adapter(harness) in the environment (a process over the sandbox's process
77
+ streams, or a worker program on a worker runtime) and hands over the ACP
78
+ stream, or hands over the URL of an endpoint already running. It refuses an
79
+ adapter that does not implement Final's ACP extensions (observe, authorMeta,
80
+ resumeReplay, afterSeqReplay), and every endpoint it hands over must advertise
81
+ them in `initialize`.
82
+
83
+ `/catalog` exports all of it as documents to publish: `finalCatalog` holds the
84
+ harness definitions, the adapter definitions and the provider.
85
+
86
+ ## Sandboxes
87
+
88
+ `/sandboxes` exports environments as plain data:
89
+ `project.sandboxes.create(computer({ image }), { setup })`.
90
+
91
+ - `computer({ image, build, machine, region, idleTimeoutSeconds })`: a Final
92
+ computer from the public Computers API. `build` steps are baked into a base once.
93
+ - `device({ deviceId })` or `device({ pairing })`: a person's own computer running
94
+ Final Device. It takes no sandbox setup.
95
+ - `workers()`: Final's Workers runtime, where the worker harnesses run. It runs
96
+ only their worker programs, offers the model no files or shell, and takes no
97
+ sandbox setup.
98
+ - `defineSandboxSetup` describes what should be on any provider's machine
99
+ (repositories, variables, steps, services, commands, network, notes); the
100
+ platform applies it after the provider attaches.
101
+
102
+ The providers (`/sandboxes/computer`, `/sandboxes/device`) follow the resource
103
+ lifecycle: `create` records a sandbox and boots nothing, `attach` reports whether
104
+ the disk is new and whether the machine booted, then `detach`, `park` and `delete`.
105
+ They take the API's origin (`computersApiUrl`, `deviceApiUrl`) and authenticate
106
+ with the provider's platform identity for it (`ctx.identity`), which the
107
+ registration must be granted. A `computer` with no `idleTimeoutSeconds` outlasts
108
+ the platform's idle parking (`ctx.lifecycle.parkAfterIdleMs`) by five minutes.
109
+
110
+ `/sandboxes/local` (Node only) is the sandbox a client serves from its own
111
+ process: files under one directory and commands run in it, confined to that
112
+ directory, inheriting only a short list of environment variables.
113
+
114
+ ## Final's MCP gateway
115
+
116
+ `finalGateway({ url })` from `/integrations` is the create-integration request
117
+ for Final's MCP gateway: `project.integrations.create(finalGateway({ url }))`,
118
+ where `url` is the gateway's MCP endpoint, for example
119
+ `https://gateway.example.com/api/mcp/tools`. The URL is required; the platform
120
+ resolves no placeholder for it. It authenticates with the platform's identity
121
+ assertion for the calling session.
33
122
 
34
123
  ## Durable processes
35
124
 
36
125
  The `computer` and `device` sandboxes offer durable processes
37
- (`SandboxCapability.DurableProcesses`: `spawn({ durable: true })` and `attachProcess`) on
38
- write and execute bindings. A ref names the sandbox, the process and its boot (and, on a
126
+ (`SandboxCapability.DurableProcesses`: `spawn({ durable: true })` and `attachProcess`) to
127
+ grants that run commands. A ref names the sandbox, the process and its boot (and, on a
39
128
  Device, the lease generation), so a ref from another sandbox, boot or generation attaches
40
129
  to nothing. A dropped handle stops polling and never terminates the process; `timeoutMs`
41
130
  is enforced by the computer or Device itself.
@@ -52,19 +141,16 @@ is enforced by the computer or Device itself.
52
141
 
53
142
  ## Workspace Device egress
54
143
 
55
- `/sandboxes/egress` supplies provider-neutral Linux TCP/UDP/DNS routing through approved Final Devices. Hosts compose `createWorkspaceDeviceNetworking({ api, needed })` through the generic provider `networking` option. They supply stable logical workspace identity, durable networking registration, the current actor, and the raw privileged sandbox session. Preserve the direct policy before boot fencing; restore it on Disable. Configure and Disable ask for consent by default.
144
+ `/sandboxes/egress` supplies provider-neutral Linux TCP/UDP/DNS routing through approved Final Devices. Hosts compose `createWorkspaceDeviceNetworking({ api, access, needed })` through the generic provider `networking` option. A sandbox's id is its workspace identity; hosts supply durable networking registration, the current actor, and the raw privileged sandbox session. Preserve the direct policy before boot fencing; restore it on Disable. Configure and Disable ask for consent by default.
56
145
 
57
- The [integration reference](WORKSPACE-EGRESS.md) specifies required hooks, boot/resume fallback, binding authority, teardown, MCP transports, failure behavior, prerequisites, and provider acceptance. Read it before enabling this capability. Final Computer and Daytona use their public APIs; custom providers can compose the same controller and manager.
146
+ The [integration reference](WORKSPACE-EGRESS.md) specifies required hooks, boot/resume fallback, grant authority, teardown, MCP transports, failure behavior, prerequisites, and provider acceptance. Read it before enabling this capability. Final Computer and Daytona use their public APIs; custom providers can compose the same controller and manager.
58
147
 
59
- In 0.4, passive Computer recovery follows the read-only viewer contract even without
60
- a networking extension. Use ordinary resume for execution. The bundled adapters
61
- cache deferred setup failures per handle; reconnect to retry rather than replaying
62
- partially completed user setup commands on each MCP poll.
148
+ A passive attach serves a read-only viewer and never boots or changes the
149
+ computer; use an ordinary attach for execution.
63
150
 
64
151
  ## Authored channels
65
152
 
66
153
  External integrations are ordinary authored modules built with `defineChannel`
67
154
  from `@finalai/agent-provider`. The SDK provides scoped platform capabilities and
68
155
  opaque credential operations; application implementations live with their source.
69
- Final chat customization uses `finalChannel` from `/channels/final` and is compiled
70
- into the authored bundle in the same way as other customization code.
156
+ Final chat customization uses `finalChannel` from `/channels/final`.
@@ -17,7 +17,7 @@ const daytona = createDaytonaSandboxProvider({ networking });
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 access in the context's credentials. A custom provider accepts
20
+ Device access through the extension's configured `access`. 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,8 +25,8 @@ 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
- The context's `credentials.deviceAccess({ actingUserId })` returns the Device API
29
- origin and a token. Device
28
+ The extension's configured `access({ organizationId, projectId, actingUserId })`
29
+ returns the Device API origin and a token. Device
30
30
  operations use the current actor; background lease reconciliation may use the stored
31
31
  lease owner instead of a present turn author. Hosts must authorize both forms of work.
32
32
  Admission, policy persistence, and cleanup also make trusted service calls with
@@ -35,7 +35,7 @@ accepted by those service endpoints for that case, separate from user-bound gran
35
35
  Returning `null` refuses the call before HTTP; a user-token-only configuration cannot
36
36
  start or clean up networked workspaces. Its host must add service authentication
37
37
  before enabling this extension. The SDK never reuses a user's cached grant for a
38
- service call or another user: it asks the context's credentials on every call.
38
+ service call or another user: it asks the configured access on every call.
39
39
 
40
40
  The extension owns Device authorization, enrollment, helper installation, leases
41
41
  and failover. Providers need no Device networking implementation. Its generic
@@ -49,7 +49,7 @@ an older API rejecting the field cannot silently grant longer access.
49
49
 
50
50
  1. Assign one stable host-owned identity per logical workspace and persist whether networking is managed. Supply that identity to start, resume, editors and teardown.
51
51
  2. Save the actual direct policy before a boot fence. Prefer a provider fence; explicitly unsupported providers use the logged startup-window fallback.
52
- 3. Construct the manager with the raw session and privileged executor, then declare its policy on the provider handle with its binding access and current-actor reader.
52
+ 3. Construct the manager with the raw session and privileged executor, then declare its policy on the provider handle with the connection's grant and current-actor reader.
53
53
  4. Detach on runtime shutdown. On physical stop/deletion, attempt fencing, lease cleanup and provider cleanup independently; retain saved desired settings for recreation.
54
54
 
55
55
  Inject the Device API URL, trusted Device credential callback, stable workspace ID, current acting user, and the raw Linux sandbox session. Pass that unguarded session into `createWorkspaceEgressManager`, then use `withWorkspaceEgress` to declare its execution policy and lifecycle hooks on the returned handle. `withWorkspaceEgress` constructs a checked handle through the generic SDK constructor, so direct callers are safe too. `defineSandboxProvider` automatically adds any host admission policy on start and resume without reinstalling the Device policy. Using a guarded session for privileged controller execution recursively deadlocks. `/sandboxes/egress-bootstrap` exports the exact controller source for custom privileged executors and manual acceptance. Custom executors run `python3 -I -c source source` with JSON on stdin, or execute a root-owned file providing `__file__`; the source argument permits persistence for later controller launches.
@@ -60,13 +60,13 @@ Supply the extension's `enabled` predicate through `createWorkspaceDeviceNetwork
60
60
 
61
61
  Before creating or booting a fenced namespace, call `preserveWorkspaceNetworkPolicy` with its actual current provider policy. This atomically saves only a missing policy; a temporary boot fence must never replace a saved choice. Both providers call the extension's `prepare` hook for durable create-if-absent behavior, and its `suspend` hook during physical teardown. The Final Device extension implements those hooks through the Device API. The initialization endpoint is deliberately separate: an older Device API rejects boot before changing provider policy rather than misinterpreting the operation as a policy update.
62
62
 
63
- Custom networking hooks own resources allocated before they reject and must clean up those resources. A failed attachment must not revoke another binding’s shared lease. The Device API must expire provisioning leases whose create acknowledgement is lost; a client cannot revoke an ID it never received, and cannot activate it without the acknowledged enrollment.
63
+ Custom networking hooks own resources allocated before they reject and must clean up those resources. A failed attachment must not revoke another connection’s shared lease. The Device API must expire provisioning leases whose create acknowledgement is lost; a client cannot revoke an ID it never received, and cannot activate it without the acknowledged enrollment.
64
64
 
65
65
  Adapters must supply `afterGuard` to restore restrictive saved policies or undo an outer fence. Missing restoration refuses Configure/Disable rather than acknowledging an unrestricted direct route. Provider instances retain remembered managed-workspace and prepared-policy identifiers for their lifetime; include that inventory in host capacity planning. Durable host registration remains required across process replacement.
66
66
 
67
67
  ## Boot, resume and saved policy
68
68
 
69
- Prefer provider networking fences before boot or rebase, then apply the transport policy only after the manager guards the namespace. Daytona defaults to a structured-warning fallback only when the account explicitly rejects custom networking; `workspaceNetworkingBootFence: true` requires the provider fence, and `false` deliberately selects command-readiness protection. Startup/resumed background traffic before controller initialization is outside the fallback guarantee. A fresh managed read binding records the intended policy before creation, then stays fenced until a write binding recovers it. Existing running read bindings remain passive. Read bindings cannot wake a managed paused/stopped Computer or stopped/archived Daytona workspace; a write binding must resume it through guarded initialization. Pass the host’s logical `session.workspaceId` through every lifecycle call. Generic `sandboxWorkspaceId` derives conversation/target identity across replacement bindings when absent; provider resource names remain private. New session workspaces use binding-derived identities and the guide's initial policy. Missing or unreadable provider policy is denied. Fallback identities escape delimiters; Device egress identities must fit the 256-character protocol limit without restricting ordinary sandbox identities; supply a bounded host identity for longer provider names. Disable before capturing an image; do not clone a live managed identity.
69
+ Prefer provider networking fences before boot or rebase, then apply the transport policy only after the manager guards the namespace. Daytona defaults to a structured-warning fallback only when the account explicitly rejects custom networking; `workspaceNetworkingBootFence: true` requires the provider fence, and `false` deliberately selects command-readiness protection. Startup/resumed background traffic before controller initialization is outside the fallback guarantee. A connection whose grant does not both write files and run commands is passive for networking: it records nothing, never recovers a route, and cannot wake a managed paused/stopped Computer or stopped/archived Daytona sandbox; a connection whose grant does both must resume it through guarded initialization. A sandbox's id is its workspace identity for Device egress; provider resource names remain private. A new sandbox starts from its setup's network policy. Missing or unreadable provider policy is denied. Fallback identities escape delimiters; Device egress identities must fit the 256-character protocol limit without restricting ordinary sandbox identities; supply a bounded host identity for longer provider names. Disable before capturing an image; do not clone a live managed identity.
70
70
 
71
71
  Device configuration and Disable ask for approval by default through the public sandbox tool policy, and can be explicitly preauthorized. Consent selects the Device’s destination controls, including private access; provider destination allowlists do not constrain traffic inside the tunnel. Retain the saved direct-access policy and restore it on Disable. Unknown initial policy prevents configuration because restoration would be undefined. Disable before changing the saved direct-access policy. In-tunnel destination allowlists are unsupported. Configure failures intentionally retain a blocked route and desired settings until successful configuration or explicit disable; they never silently restore direct access.
72
72
 
@@ -93,13 +93,13 @@ A workspace shares one network namespace; checks on new operations do not isolat
93
93
  already-running processes by user. The Linux firewall independently enforces
94
94
  physical-egress denial.
95
95
 
96
- ## Authority, bindings and shutdown
96
+ ## Authority, grants and shutdown
97
97
 
98
- Device Configure/Disable require write access and lifecycle authority. Direct network-policy operations require execute or write access plus lifecycle authority; read bindings cannot mutate networking. MCP projections enforce tool consent; hosts exposing handle RPC must install their own operation-consent hook. Configure and Disable default to Ask. Only exact `network.egress.configure`/`network.egress.disable` action preauthorization can waive the default; broad lifecycle/network Allow cannot. Status honors `network.egress.status` and `egress` policy. MCP hosts pass lifecycle authority and action input to the policy-key helper, then use `resolveSandboxToolPolicy` so any applicable Ask wins for Configure/Disable. Status retains ordinary first-match read-policy precedence for `network.egress.status` and `egress`. This release exposes status on write/lifecycle bindings. Foreground actor changes suspend as the currently acting user, including a trusted userless host, and block until an explicit route choice; background bindings do not revoke another actor's chosen route. Userless execution cannot inherit someone else's Device access.
98
+ Device Configure/Disable require write access and lifecycle authority. Direct network-policy operations require execute or write access plus lifecycle authority; a grant without files write and shell execute cannot mutate networking. MCP projections enforce tool consent; hosts exposing handle RPC must install their own operation-consent hook. Configure and Disable default to Ask. Only exact `network.egress.configure`/`network.egress.disable` action preauthorization can waive the default; broad lifecycle/network Allow cannot. Status honors `network.egress.status` and `egress` policy. MCP hosts pass lifecycle authority and action input to the policy-key helper, then use `resolveSandboxToolPolicy` so any applicable Ask wins for Configure/Disable. Status retains ordinary first-match read-policy precedence for `network.egress.status` and `egress`. This release exposes status to grants that write files and run commands, with lifecycle authority. Foreground actor changes suspend as the currently acting user, including a trusted userless host, and block until an explicit route choice; background connections do not revoke another actor's chosen route. Userless execution cannot inherit someone else's Device access.
99
99
 
100
- Read bindings skip recovery and deferred setup. Pass binding access to `withWorkspaceEgress`; never initialize a manager for a read binding. They may inspect files but cannot repair a blocked route or run setup. A write binding must configure or explicitly disable first. Networking changes do not run deferred repository/setup work; that runs before the next guarded execution, write, or foreground MCP endpoint/channel acquisition.
100
+ Passive connections skip recovery and deferred setup. Pass the connection's grant to `withWorkspaceEgress`; never initialize a manager for a passive connection. They may inspect files but cannot repair a blocked route or run setup. A connection whose grant writes files and runs commands must configure or explicitly disable first. Networking changes do not run deferred repository/setup work; that runs before the next guarded execution, write, or foreground MCP endpoint/channel acquisition.
101
101
 
102
- The manager owns renewal; failure observers cannot interrupt networking or claim cleanup. Editor access by another actor is refused before changing the owner’s route. A successful direct-policy retry clears a previous provider-update failure, including for userless workspaces. `close()` detaches a handle; runtime shutdown must not disrupt another binding sharing the workspace. `close(true)` attempts the namespace fence and suspends every lease for an explicit workspace stop/deletion, even if its cached lease has been replaced. Failure cleanup revokes only a lease that this handle created. Provider teardown independently attempts the fence, resource cleanup and lease revocation, and reports failures after cleanup. Removing boot admission must still permit teardown; hosts distinguish it with the predicate's `teardown` flag. Desired settings survive stop and lease expiry.
102
+ The manager owns renewal; failure observers cannot interrupt networking or claim cleanup. Editor access by another actor is refused before changing the owner’s route. A successful direct-policy retry clears a previous provider-update failure, including for userless workspaces. `close()` detaches a handle; runtime shutdown must not disrupt another connection sharing the workspace. `close(true)` attempts the namespace fence and suspends every lease for an explicit workspace stop/deletion, even if its cached lease has been replaced. Failure cleanup revokes only a lease that this handle created. Provider teardown independently attempts the fence, resource cleanup and lease revocation, and reports failures after cleanup. Removing boot admission must still permit teardown; hosts distinguish it with the predicate's `teardown` flag. Desired settings survive stop and lease expiry.
103
103
 
104
104
  Teardown preserves the current provider policy before applying a fence. If eligibility or initial-policy persistence is unavailable for an unknown workspace, teardown still stops the resource, attempts suspension, and reports the error; it does not overwrite an unrelated policy. Host-managed boot waits for eligibility and saved policy recovery. Managed Computer creation and write/resume disable automatic wake. Passive read attachments do not change provider settings; migrate existing resources through a write attachment before relying on managed wake protection. Daytona’s preferred-fence fallback retains the documented startup window.
105
105
 
@@ -0,0 +1,9 @@
1
+ import type { DefinedHarnessProviderIface } from '@finalai/agent-definition/authoring';
2
+ import type { AcpAdapterDefinitionIface, HarnessDefinitionIface } from '@finalai/agent-provider';
3
+ import { FinalHarnessName, type FinalAcpAdapter } from "./harnesses/names.mjs";
4
+ export interface FinalCatalogIface {
5
+ harnesses: Record<FinalHarnessName, HarnessDefinitionIface>;
6
+ adapters: Record<FinalAcpAdapter, AcpAdapterDefinitionIface>;
7
+ provider: DefinedHarnessProviderIface;
8
+ }
9
+ export declare const finalCatalog: FinalCatalogIface;
@@ -0,0 +1,21 @@
1
+ import { FINAL_ACP_ADAPTERS } from "./harnesses/adapters.mjs";
2
+ import { FINAL_HARNESS_DEFINITIONS } from "./harnesses/definitions.mjs";
3
+ import { FinalHarness, FinalHarnessName } from "./harnesses/names.mjs";
4
+ import { GENERAL_HARNESS_PROVIDER } from "./provider/index.mjs";
5
+ const finalCatalog = {
6
+ harnesses: {
7
+ [FinalHarnessName.Final]: FINAL_HARNESS_DEFINITIONS[FinalHarness.Final],
8
+ [FinalHarnessName.Pi]: FINAL_HARNESS_DEFINITIONS[FinalHarness.Pi],
9
+ [FinalHarnessName.NanocodeClaude]: FINAL_HARNESS_DEFINITIONS[FinalHarness.NanocodeClaude],
10
+ [FinalHarnessName.NanocodeCodex]: FINAL_HARNESS_DEFINITIONS[FinalHarness.NanocodeCodex],
11
+ [FinalHarnessName.ClaudeCode]: FINAL_HARNESS_DEFINITIONS[FinalHarness.ClaudeCode],
12
+ [FinalHarnessName.Codex]: FINAL_HARNESS_DEFINITIONS[FinalHarness.Codex],
13
+ [FinalHarnessName.AcpAgent]: FINAL_HARNESS_DEFINITIONS[FinalHarness.AcpAgent]
14
+ },
15
+ adapters: FINAL_ACP_ADAPTERS,
16
+ provider: GENERAL_HARNESS_PROVIDER
17
+ };
18
+ export {
19
+ finalCatalog
20
+ };
21
+ //# sourceMappingURL=catalog.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/catalog.ts"],
4
+ "sourcesContent": ["import type { DefinedHarnessProviderIface } from '@finalai/agent-definition/authoring';\nimport type { AcpAdapterDefinitionIface, HarnessDefinitionIface } from '@finalai/agent-provider';\nimport { FINAL_ACP_ADAPTERS } from \"./harnesses/adapters.mjs\";\nimport { FINAL_HARNESS_DEFINITIONS } from \"./harnesses/definitions.mjs\";\nimport { FinalHarness, FinalHarnessName, type FinalAcpAdapter } from \"./harnesses/names.mjs\";\nimport { GENERAL_HARNESS_PROVIDER } from \"./provider/index.mjs\";\n\n// Everything Final publishes for harnesses, as documents: its harness and ACP\n// adapter definitions by the names they are published under (settings reach\n// them qualified, `final/<name>`), and the general provider that runs any\n// definition carrying no provider of its own.\nexport interface FinalCatalogIface {\n harnesses: Record<FinalHarnessName, HarnessDefinitionIface>;\n adapters: Record<FinalAcpAdapter, AcpAdapterDefinitionIface>;\n provider: DefinedHarnessProviderIface;\n}\n\nexport const finalCatalog: FinalCatalogIface = {\n harnesses: {\n [FinalHarnessName.Final]: FINAL_HARNESS_DEFINITIONS[FinalHarness.Final],\n [FinalHarnessName.Pi]: FINAL_HARNESS_DEFINITIONS[FinalHarness.Pi],\n [FinalHarnessName.NanocodeClaude]: FINAL_HARNESS_DEFINITIONS[FinalHarness.NanocodeClaude],\n [FinalHarnessName.NanocodeCodex]: FINAL_HARNESS_DEFINITIONS[FinalHarness.NanocodeCodex],\n [FinalHarnessName.ClaudeCode]: FINAL_HARNESS_DEFINITIONS[FinalHarness.ClaudeCode],\n [FinalHarnessName.Codex]: FINAL_HARNESS_DEFINITIONS[FinalHarness.Codex],\n [FinalHarnessName.AcpAgent]: FINAL_HARNESS_DEFINITIONS[FinalHarness.AcpAgent],\n },\n adapters: FINAL_ACP_ADAPTERS,\n provider: GENERAL_HARNESS_PROVIDER,\n};\n"],
5
+ "mappings": "AAEA,SAAS,0BAA0B;AACnC,SAAS,iCAAiC;AAC1C,SAAS,cAAc,wBAA8C;AACrE,SAAS,gCAAgC;AAYlC,MAAM,eAAkC;AAAA,EAC7C,WAAW;AAAA,IACT,CAAC,iBAAiB,KAAK,GAAG,0BAA0B,aAAa,KAAK;AAAA,IACtE,CAAC,iBAAiB,EAAE,GAAG,0BAA0B,aAAa,EAAE;AAAA,IAChE,CAAC,iBAAiB,cAAc,GAAG,0BAA0B,aAAa,cAAc;AAAA,IACxF,CAAC,iBAAiB,aAAa,GAAG,0BAA0B,aAAa,aAAa;AAAA,IACtF,CAAC,iBAAiB,UAAU,GAAG,0BAA0B,aAAa,UAAU;AAAA,IAChF,CAAC,iBAAiB,KAAK,GAAG,0BAA0B,aAAa,KAAK;AAAA,IACtE,CAAC,iBAAiB,QAAQ,GAAG,0BAA0B,aAAa,QAAQ;AAAA,EAC9E;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AACZ;",
6
+ "names": []
7
+ }
@@ -95,7 +95,13 @@ const accumulateUpdate = (state, update) => {
95
95
  const block = contentBlockOf(chunk.data.content);
96
96
  if (block == null) return null;
97
97
  const segment = segmentFor(state, chunk.data.messageId);
98
- segment.blocks.push(jsonOfContentBlock(block));
98
+ const last = segment.blocks.at(-1);
99
+ const lastBlock = last == null ? null : contentBlockOf(last);
100
+ const lastText = lastBlock == null ? null : plainText(lastBlock);
101
+ const nextText = plainText(block);
102
+ if (lastText != null && nextText != null)
103
+ segment.blocks[segment.blocks.length - 1] = { type: "text", text: `${lastText}${nextText}` };
104
+ else segment.blocks.push(jsonOfContentBlock(block));
99
105
  return segment;
100
106
  }
101
107
  const message = agentMessageSchema.safeParse(json);
@@ -135,17 +141,18 @@ const createProjector = (config = {}) => {
135
141
  }
136
142
  const target = state.messageId == null ? segment : state;
137
143
  target.lastSeq = ctx.seq;
138
- const content = visibleContent({ segments: target === state ? state.segments : [segment] });
139
- if (content.length === 0 && target.messageId == null) {
140
- ctx.state.set(state);
141
- return;
142
- }
144
+ const content = () => visibleContent({ segments: target === state ? state.segments : [segment] });
143
145
  if (target.messageId == null) {
146
+ const visible = content();
147
+ if (visible.length === 0) {
148
+ ctx.state.set(state);
149
+ return;
150
+ }
144
151
  const message = await ctx.platform.conversations.postMessage({
145
152
  conversationId: ctx.conversation.conversationId,
146
153
  authorParticipantId: ctx.agentParticipant.id,
147
154
  kind: MessageKind.Message,
148
- content,
155
+ content: visible,
149
156
  source: { turnId: ctx.turn.id, fromSeq: ctx.seq, toSeq: ctx.seq }
150
157
  });
151
158
  target.messageId = message.messageId;
@@ -156,7 +163,7 @@ const createProjector = (config = {}) => {
156
163
  } else if (now() - target.lastFlushAt >= interval) {
157
164
  await ctx.platform.conversations.updateMessage({
158
165
  messageId: target.messageId,
159
- content,
166
+ content: content(),
160
167
  final: false
161
168
  });
162
169
  target.lastFlushAt = now();
@@ -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// 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 // RoomSurfaceDefinitionIface['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 (state.messageId == null && (segment == null || segment.messageId == null)) {\n for (const previous of state.segments) {\n if (previous === segment || !previous.dirty || previous.messageId == null) continue;\n await ctx.platform.conversations.updateMessage({\n messageId: previous.messageId,\n content: visibleContent({ segments: [previous] }),\n final: false,\n });\n previous.lastFlushAt = now();\n previous.dirty = false;\n }\n }\n if (segment == null) {\n ctx.state.set(state);\n return;\n }\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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",
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 // Plain text joins the run before it as it arrives (visibleContent joins\n // them the same way), so the state and each fold stay the same size\n // however long the reply grows.\n const last = segment.blocks.at(-1);\n const lastBlock = last == null ? null : contentBlockOf(last);\n const lastText = lastBlock == null ? null : plainText(lastBlock);\n const nextText = plainText(block);\n if (lastText != null && nextText != null)\n segment.blocks[segment.blocks.length - 1] = { type: 'text', text: `${lastText}${nextText}` };\n else 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 // RoomSurfaceDefinitionIface['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 (state.messageId == null && (segment == null || segment.messageId == null)) {\n for (const previous of state.segments) {\n if (previous === segment || !previous.dirty || previous.messageId == null) continue;\n await ctx.platform.conversations.updateMessage({\n messageId: previous.messageId,\n content: visibleContent({ segments: [previous] }),\n final: false,\n });\n previous.lastFlushAt = now();\n previous.dirty = false;\n }\n }\n if (segment == null) {\n ctx.state.set(state);\n return;\n }\n const target = state.messageId == null ? segment : state;\n target.lastSeq = ctx.seq;\n // Built only when it is written: between flushes a chunk only folds.\n const content = () =>\n visibleContent({ segments: target === state ? state.segments : [segment] });\n if (target.messageId == null) {\n const visible = content();\n if (visible.length === 0) {\n ctx.state.set(state);\n return;\n }\n const message = await ctx.platform.conversations.postMessage({\n conversationId: ctx.conversation.conversationId,\n authorParticipantId: ctx.agentParticipant.id,\n kind: MessageKind.Message,\n content: visible,\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: 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
  }
@@ -0,0 +1,4 @@
1
+ import { type HarnessDefinitionIface, type HarnessSettingsIface } from '@finalai/agent-provider';
2
+ import { type AtUrlInputIface } from "./names.mjs";
3
+ export declare const ACP_AGENT_HARNESS: HarnessDefinitionIface;
4
+ export declare const acpAgent: (input: AtUrlInputIface) => HarnessSettingsIface;
@@ -0,0 +1,35 @@
1
+ import { defineHarness } from "@finalai/agent-definition/authoring";
2
+ import {
3
+ ACP_V2_INTERFACE,
4
+ CredentialDelivery,
5
+ HarnessFeature,
6
+ InferenceSourceKind,
7
+ McpTransportKind,
8
+ PermissionLevel,
9
+ PromptWhileRunning
10
+ } from "@finalai/agent-provider";
11
+ import { configObjectJson, harnessSettings } from "./config-fields.mjs";
12
+ import { atUrl, FinalHarness } from "./names.mjs";
13
+ const ACP_AGENT_HARNESS = defineHarness({
14
+ title: "ACP agent",
15
+ description: "An agent that speaks ACP v2 with Final's ACP extensions, at its URL.",
16
+ configurationSchema: configObjectJson({ title: "ACP agent", properties: {} }),
17
+ features: [HarnessFeature.McpTools],
18
+ // Final cannot hold an agent it does not run to asking first.
19
+ lowestPermission: PermissionLevel.Auto,
20
+ inference: { kinds: [InferenceSourceKind.Harness], delivery: CredentialDelivery.AtOpen },
21
+ interface: ACP_V2_INTERFACE,
22
+ adapter: null,
23
+ run: {},
24
+ mcp: [McpTransportKind.Http],
25
+ // The extensions every endpoint Final dials must advertise.
26
+ supports: { observe: true, resumeReplay: true, authorMeta: true, fork: false },
27
+ promptWhileRunning: PromptWhileRunning.Queue,
28
+ sessionSurvivesDisconnect: false
29
+ });
30
+ const acpAgent = (input) => harnessSettings({ harness: FinalHarness.AcpAgent, configuration: {}, environment: atUrl(input) });
31
+ export {
32
+ ACP_AGENT_HARNESS,
33
+ acpAgent
34
+ };
35
+ //# sourceMappingURL=acp-agent.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/harnesses/acp-agent.ts"],
4
+ "sourcesContent": ["import { defineHarness } from '@finalai/agent-definition/authoring';\nimport {\n ACP_V2_INTERFACE,\n CredentialDelivery,\n HarnessFeature,\n InferenceSourceKind,\n McpTransportKind,\n PermissionLevel,\n PromptWhileRunning,\n type HarnessDefinitionIface,\n type HarnessSettingsIface,\n} from '@finalai/agent-provider';\nimport { configObjectJson, harnessSettings } from \"./config-fields.mjs\";\nimport { atUrl, FinalHarness, type AtUrlInputIface } from \"./names.mjs\";\n\n// Any agent that already speaks ACP v2 with Final's ACP extensions, running\n// elsewhere: it needs no adapter, nothing is started, and it is reached at its\n// URL (an `atUrl` environment). It brings its own model access and is told\n// nothing about Final's credentials; what it does beyond ACP is its own, so\n// the definition promises no more than ACP and the required extensions give.\n\nexport const ACP_AGENT_HARNESS: HarnessDefinitionIface = defineHarness({\n title: 'ACP agent',\n description: \"An agent that speaks ACP v2 with Final's ACP extensions, at its URL.\",\n configurationSchema: configObjectJson({ title: 'ACP agent', properties: {} }),\n features: [HarnessFeature.McpTools],\n // Final cannot hold an agent it does not run to asking first.\n lowestPermission: PermissionLevel.Auto,\n inference: { kinds: [InferenceSourceKind.Harness], delivery: CredentialDelivery.AtOpen },\n interface: ACP_V2_INTERFACE,\n adapter: null,\n run: {},\n mcp: [McpTransportKind.Http],\n // The extensions every endpoint Final dials must advertise.\n supports: { observe: true, resumeReplay: true, authorMeta: true, fork: false },\n promptWhileRunning: PromptWhileRunning.Queue,\n sessionSurvivesDisconnect: false,\n});\n\n// An ACP agent at `url`; `hop` names the project secret holding its bearer token.\nexport const acpAgent = (input: AtUrlInputIface): HarnessSettingsIface =>\n harnessSettings({ harness: FinalHarness.AcpAgent, configuration: {}, environment: atUrl(input) });\n"],
5
+ "mappings": "AAAA,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,kBAAkB,uBAAuB;AAClD,SAAS,OAAO,oBAA0C;AAQnD,MAAM,oBAA4C,cAAc;AAAA,EACrE,OAAO;AAAA,EACP,aAAa;AAAA,EACb,qBAAqB,iBAAiB,EAAE,OAAO,aAAa,YAAY,CAAC,EAAE,CAAC;AAAA,EAC5E,UAAU,CAAC,eAAe,QAAQ;AAAA;AAAA,EAElC,kBAAkB,gBAAgB;AAAA,EAClC,WAAW,EAAE,OAAO,CAAC,oBAAoB,OAAO,GAAG,UAAU,mBAAmB,OAAO;AAAA,EACvF,WAAW;AAAA,EACX,SAAS;AAAA,EACT,KAAK,CAAC;AAAA,EACN,KAAK,CAAC,iBAAiB,IAAI;AAAA;AAAA,EAE3B,UAAU,EAAE,SAAS,MAAM,cAAc,MAAM,YAAY,MAAM,MAAM,MAAM;AAAA,EAC7E,oBAAoB,mBAAmB;AAAA,EACvC,2BAA2B;AAC7B,CAAC;AAGM,MAAM,WAAW,CAAC,UACvB,gBAAgB,EAAE,SAAS,aAAa,UAAU,eAAe,CAAC,GAAG,aAAa,MAAM,KAAK,EAAE,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,7 @@
1
+ import { type AcpAdapterDefinitionIface } from '@finalai/agent-provider';
2
+ import { FinalAcpAdapter } from "./names.mjs";
3
+ export declare const PI_ACP_ADAPTER: AcpAdapterDefinitionIface;
4
+ export declare const NANOCODE_ACP_ADAPTER: AcpAdapterDefinitionIface;
5
+ export declare const CLAUDE_CODE_ACP_ADAPTER: AcpAdapterDefinitionIface;
6
+ export declare const CODEX_ACP_ADAPTER: AcpAdapterDefinitionIface;
7
+ export declare const FINAL_ACP_ADAPTERS: Record<FinalAcpAdapter, AcpAdapterDefinitionIface>;
@@ -0,0 +1,74 @@
1
+ import { defineAcpAdapter } from "@finalai/agent-definition/authoring";
2
+ import { FinalExtension } from "@finalai/agent-provider";
3
+ import { REQUIRED_FINAL_EXTENSIONS } from "../provider/general.mjs";
4
+ import { FinalAcpAdapter, FinalHarnessInterface } from "./names.mjs";
5
+ const PI_ACP_ADAPTER = defineAcpAdapter({
6
+ title: "Pi ACP adapter",
7
+ description: "Maps the Pi agent loop (Pi and the Final harness) to ACP v2 on a worker runtime.",
8
+ adapts: [FinalHarnessInterface.PiAgent],
9
+ run: { worker: { program: "pi" } },
10
+ extensions: [...REQUIRED_FINAL_EXTENSIONS]
11
+ });
12
+ const NANOCODE_ACP_ADAPTER = defineAcpAdapter({
13
+ title: "nanocode ACP adapter",
14
+ description: "Maps nanocode's Claude and Codex engines to ACP v2 on a worker runtime.",
15
+ adapts: [FinalHarnessInterface.Nanocode],
16
+ run: { worker: { program: "nanocode" } },
17
+ extensions: [...REQUIRED_FINAL_EXTENSIONS]
18
+ });
19
+ const permissionArgs = {
20
+ when: "configuration.permissionMode",
21
+ args: ["--permission-mode", "{{configuration.permissionMode}}"]
22
+ };
23
+ const CLAUDE_CODE_ACP_ADAPTER = defineAcpAdapter({
24
+ title: "Claude Code ACP adapter",
25
+ description: "Runs Claude Code through the Claude Agent SDK and speaks ACP v2 on stdio.",
26
+ adapts: [FinalHarnessInterface.ClaudeAgentSdk],
27
+ run: {
28
+ process: {
29
+ command: "npx",
30
+ args: [
31
+ "-y",
32
+ "@finalai/acp-adapter-claude-code",
33
+ permissionArgs,
34
+ "--",
35
+ "{{harness.command}}",
36
+ { spread: "harness.args" }
37
+ ]
38
+ }
39
+ },
40
+ extensions: [...REQUIRED_FINAL_EXTENSIONS, FinalExtension.Fork]
41
+ });
42
+ const CODEX_ACP_ADAPTER = defineAcpAdapter({
43
+ title: "Codex ACP adapter",
44
+ description: "Runs the Codex app-server and speaks ACP v2 on stdio.",
45
+ adapts: [FinalHarnessInterface.CodexAppServer],
46
+ run: {
47
+ process: {
48
+ command: "npx",
49
+ args: [
50
+ "-y",
51
+ "@finalai/acp-adapter-codex",
52
+ permissionArgs,
53
+ "--",
54
+ "{{harness.command}}",
55
+ { spread: "harness.args" }
56
+ ]
57
+ }
58
+ },
59
+ extensions: [...REQUIRED_FINAL_EXTENSIONS]
60
+ });
61
+ const FINAL_ACP_ADAPTERS = {
62
+ [FinalAcpAdapter.Pi]: PI_ACP_ADAPTER,
63
+ [FinalAcpAdapter.Nanocode]: NANOCODE_ACP_ADAPTER,
64
+ [FinalAcpAdapter.ClaudeCode]: CLAUDE_CODE_ACP_ADAPTER,
65
+ [FinalAcpAdapter.Codex]: CODEX_ACP_ADAPTER
66
+ };
67
+ export {
68
+ CLAUDE_CODE_ACP_ADAPTER,
69
+ CODEX_ACP_ADAPTER,
70
+ FINAL_ACP_ADAPTERS,
71
+ NANOCODE_ACP_ADAPTER,
72
+ PI_ACP_ADAPTER
73
+ };
74
+ //# sourceMappingURL=adapters.mjs.map