@elie-laloum/outpost 4.0.0 → 4.2.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 (240) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +10 -6
  3. package/SECURITY.md +2 -0
  4. package/dist/adapters/agents/codex-adapter.js +2 -0
  5. package/dist/adapters/agents/codex-adapter.js.map +1 -1
  6. package/dist/adapters/agents/codex-provider.d.ts +2 -0
  7. package/dist/adapters/agents/codex-provider.js +32 -0
  8. package/dist/adapters/agents/codex-provider.js.map +1 -0
  9. package/dist/adapters/agents/codex-request.js +2 -1
  10. package/dist/adapters/agents/codex-request.js.map +1 -1
  11. package/dist/adapters/agents/gemini-request.js +2 -0
  12. package/dist/adapters/agents/gemini-request.js.map +1 -1
  13. package/dist/adapters/agents/settings.types.d.ts +5 -0
  14. package/dist/adapters/agents/settings.types.js.map +1 -1
  15. package/dist/adapters/models/openai-compatible.constants.d.ts +4 -0
  16. package/dist/adapters/models/openai-compatible.constants.js +10 -0
  17. package/dist/adapters/models/openai-compatible.constants.js.map +1 -0
  18. package/dist/adapters/models/openai-compatible.d.ts +3 -0
  19. package/dist/adapters/models/openai-compatible.js +78 -0
  20. package/dist/adapters/models/openai-compatible.js.map +1 -0
  21. package/dist/adapters/models/openai-compatible.types.d.ts +14 -0
  22. package/dist/adapters/models/openai-compatible.types.js +2 -0
  23. package/dist/adapters/models/openai-compatible.types.js.map +1 -0
  24. package/dist/adapters/models/openai-http.d.ts +1 -0
  25. package/dist/adapters/models/openai-http.js +30 -0
  26. package/dist/adapters/models/openai-http.js.map +1 -0
  27. package/dist/adapters/models/openai-request.d.ts +4 -0
  28. package/dist/adapters/models/openai-request.js +46 -0
  29. package/dist/adapters/models/openai-request.js.map +1 -0
  30. package/dist/adapters/models/openai-response.d.ts +3 -0
  31. package/dist/adapters/models/openai-response.js +65 -0
  32. package/dist/adapters/models/openai-response.js.map +1 -0
  33. package/dist/application/execution.types.d.ts +2 -0
  34. package/dist/application/execution.types.js.map +1 -1
  35. package/dist/application/outpost.types.d.ts +5 -0
  36. package/dist/application/outpost.types.js.map +1 -1
  37. package/dist/application/recovery-archive.d.ts +4 -0
  38. package/dist/application/recovery-archive.js +29 -0
  39. package/dist/application/recovery-archive.js.map +1 -0
  40. package/dist/application/recovery-archive.types.d.ts +10 -0
  41. package/dist/application/recovery-archive.types.js +2 -0
  42. package/dist/application/recovery-archive.types.js.map +1 -0
  43. package/dist/application/recovery-inspection.js +3 -0
  44. package/dist/application/recovery-inspection.js.map +1 -1
  45. package/dist/application/recovery-inspection.types.d.ts +2 -0
  46. package/dist/application/recovery-inspection.types.js.map +1 -1
  47. package/dist/application/recovery-retention.d.ts +2 -1
  48. package/dist/application/recovery-retention.js +9 -1
  49. package/dist/application/recovery-retention.js.map +1 -1
  50. package/dist/application/recovery-retention.types.d.ts +6 -0
  51. package/dist/application/recovery-retention.types.js.map +1 -1
  52. package/dist/application/remote-backup.js +8 -0
  53. package/dist/application/remote-backup.js.map +1 -1
  54. package/dist/application/remote-workspace.types.d.ts +2 -0
  55. package/dist/application/remote-workspace.types.js.map +1 -1
  56. package/dist/application/sandbox-agents.js +2 -1
  57. package/dist/application/sandbox-agents.js.map +1 -1
  58. package/dist/application/sandbox-dispatch.js +9 -0
  59. package/dist/application/sandbox-dispatch.js.map +1 -1
  60. package/dist/application/sandbox-provision.js +6 -0
  61. package/dist/application/sandbox-provision.js.map +1 -1
  62. package/dist/application/storage-reservation.js +3 -0
  63. package/dist/application/storage-reservation.js.map +1 -1
  64. package/dist/application/transport-inspection.constants.d.ts +2 -0
  65. package/dist/application/transport-inspection.constants.js +10 -0
  66. package/dist/application/transport-inspection.constants.js.map +1 -0
  67. package/dist/application/transport-inspection.d.ts +2 -0
  68. package/dist/application/transport-inspection.js +103 -0
  69. package/dist/application/transport-inspection.js.map +1 -0
  70. package/dist/application/transport-retention.d.ts +4 -0
  71. package/dist/application/transport-retention.js +153 -0
  72. package/dist/application/transport-retention.js.map +1 -0
  73. package/dist/cli/commands.js +55 -18
  74. package/dist/cli/commands.js.map +1 -1
  75. package/dist/cli/init-authentication.constants.d.ts +13 -0
  76. package/dist/cli/init-authentication.constants.js +10 -0
  77. package/dist/cli/init-authentication.constants.js.map +1 -0
  78. package/dist/cli/init-authentication.d.ts +4 -0
  79. package/dist/cli/init-authentication.js +43 -0
  80. package/dist/cli/init-authentication.js.map +1 -0
  81. package/dist/cli/init-command.js +74 -15
  82. package/dist/cli/init-command.js.map +1 -1
  83. package/dist/cli/main.constants.d.ts +102 -2
  84. package/dist/cli/main.constants.js +148 -46
  85. package/dist/cli/main.constants.js.map +1 -1
  86. package/dist/cli/scaffold-files.js +4 -3
  87. package/dist/cli/scaffold-files.js.map +1 -1
  88. package/dist/cli/scaffold-validation.js +19 -0
  89. package/dist/cli/scaffold-validation.js.map +1 -1
  90. package/dist/cli/scaffold.constants.d.ts +0 -5
  91. package/dist/cli/scaffold.constants.js +0 -5
  92. package/dist/cli/scaffold.constants.js.map +1 -1
  93. package/dist/cli/scaffold.js +13 -0
  94. package/dist/cli/scaffold.js.map +1 -1
  95. package/dist/cli/scaffold.types.d.ts +3 -0
  96. package/dist/cli/scaffold.types.js.map +1 -1
  97. package/dist/cli/starters.js +9 -2
  98. package/dist/cli/starters.js.map +1 -1
  99. package/dist/domain/conversation.types.d.ts +2 -0
  100. package/dist/domain/conversation.types.js.map +1 -1
  101. package/dist/domain/model.types.d.ts +15 -0
  102. package/dist/domain/model.types.js +2 -0
  103. package/dist/domain/model.types.js.map +1 -0
  104. package/dist/domain/transport.constants.d.ts +2 -0
  105. package/dist/domain/transport.constants.js +3 -0
  106. package/dist/domain/transport.constants.js.map +1 -0
  107. package/dist/domain/transport.d.ts +6 -0
  108. package/dist/domain/transport.js +21 -0
  109. package/dist/domain/transport.js.map +1 -0
  110. package/dist/domain/transport.types.d.ts +31 -0
  111. package/dist/domain/transport.types.js +2 -0
  112. package/dist/domain/transport.types.js.map +1 -0
  113. package/dist/index.d.ts +18 -1
  114. package/dist/index.js +8 -0
  115. package/dist/index.js.map +1 -1
  116. package/dist/infrastructure/artifact-store.js +10 -0
  117. package/dist/infrastructure/artifact-store.js.map +1 -1
  118. package/dist/infrastructure/artifact-store.types.d.ts +3 -1
  119. package/dist/infrastructure/artifact-store.types.js.map +1 -1
  120. package/dist/infrastructure/conversations/capture.js +1 -1
  121. package/dist/infrastructure/conversations/capture.js.map +1 -1
  122. package/dist/infrastructure/conversations.d.ts +2 -0
  123. package/dist/infrastructure/conversations.js +2 -0
  124. package/dist/infrastructure/conversations.js.map +1 -1
  125. package/dist/infrastructure/inspection-file.js +3 -3
  126. package/dist/infrastructure/inspection-file.js.map +1 -1
  127. package/dist/infrastructure/inspection-hash.js +4 -3
  128. package/dist/infrastructure/inspection-hash.js.map +1 -1
  129. package/dist/infrastructure/inspection-path.d.ts +1 -0
  130. package/dist/infrastructure/inspection-path.js +18 -0
  131. package/dist/infrastructure/inspection-path.js.map +1 -0
  132. package/dist/infrastructure/journal.js +6 -0
  133. package/dist/infrastructure/journal.js.map +1 -1
  134. package/dist/infrastructure/journal.types.d.ts +3 -0
  135. package/dist/infrastructure/journal.types.js.map +1 -1
  136. package/dist/infrastructure/local-transport.d.ts +3 -0
  137. package/dist/infrastructure/local-transport.js +146 -0
  138. package/dist/infrastructure/local-transport.js.map +1 -0
  139. package/dist/infrastructure/local-transport.types.d.ts +3 -0
  140. package/dist/infrastructure/local-transport.types.js +2 -0
  141. package/dist/infrastructure/local-transport.types.js.map +1 -0
  142. package/dist/infrastructure/resource-activity-inspection.d.ts +2 -1
  143. package/dist/infrastructure/resource-activity-inspection.js +2 -2
  144. package/dist/infrastructure/resource-activity-inspection.js.map +1 -1
  145. package/dist/infrastructure/resource-activity.js +42 -14
  146. package/dist/infrastructure/resource-activity.js.map +1 -1
  147. package/dist/infrastructure/resource-activity.types.d.ts +2 -0
  148. package/dist/infrastructure/resource-activity.types.js.map +1 -1
  149. package/dist/infrastructure/s3-transport.d.ts +4 -0
  150. package/dist/infrastructure/s3-transport.js +144 -0
  151. package/dist/infrastructure/s3-transport.js.map +1 -0
  152. package/dist/infrastructure/s3-transport.types.d.ts +6 -0
  153. package/dist/infrastructure/s3-transport.types.js +2 -0
  154. package/dist/infrastructure/s3-transport.types.js.map +1 -0
  155. package/dist/infrastructure/storage-inventory.types.d.ts +2 -1
  156. package/dist/infrastructure/storage-inventory.types.js.map +1 -1
  157. package/dist/infrastructure/storage-reservations.types.d.ts +2 -0
  158. package/dist/infrastructure/storage-reservations.types.js.map +1 -1
  159. package/dist/infrastructure/task-queue-bullmq-resources.d.ts +13 -0
  160. package/dist/infrastructure/task-queue-bullmq-resources.js +118 -0
  161. package/dist/infrastructure/task-queue-bullmq-resources.js.map +1 -0
  162. package/dist/infrastructure/task-queue-bullmq-state.d.ts +5 -0
  163. package/dist/infrastructure/task-queue-bullmq-state.js +26 -0
  164. package/dist/infrastructure/task-queue-bullmq-state.js.map +1 -0
  165. package/dist/infrastructure/task-queue-bullmq.constants.d.ts +7 -0
  166. package/dist/infrastructure/task-queue-bullmq.constants.js +79 -0
  167. package/dist/infrastructure/task-queue-bullmq.constants.js.map +1 -0
  168. package/dist/infrastructure/task-queue-bullmq.d.ts +3 -0
  169. package/dist/infrastructure/task-queue-bullmq.js +124 -0
  170. package/dist/infrastructure/task-queue-bullmq.js.map +1 -0
  171. package/dist/infrastructure/task-queue-bullmq.types.d.ts +17 -0
  172. package/dist/infrastructure/task-queue-bullmq.types.js +2 -0
  173. package/dist/infrastructure/task-queue-bullmq.types.js.map +1 -0
  174. package/dist/infrastructure/transport-archive.constants.d.ts +6 -0
  175. package/dist/infrastructure/transport-archive.constants.js +7 -0
  176. package/dist/infrastructure/transport-archive.constants.js.map +1 -0
  177. package/dist/infrastructure/transport-archive.d.ts +5 -0
  178. package/dist/infrastructure/transport-archive.js +157 -0
  179. package/dist/infrastructure/transport-archive.js.map +1 -0
  180. package/dist/infrastructure/transport-archive.types.d.ts +17 -0
  181. package/dist/infrastructure/transport-archive.types.js +2 -0
  182. package/dist/infrastructure/transport-archive.types.js.map +1 -0
  183. package/dist/infrastructure/transport-artifact-store.d.ts +3 -0
  184. package/dist/infrastructure/transport-artifact-store.js +31 -0
  185. package/dist/infrastructure/transport-artifact-store.js.map +1 -0
  186. package/dist/infrastructure/transport-artifact-store.types.d.ts +4 -0
  187. package/dist/infrastructure/transport-artifact-store.types.js +2 -0
  188. package/dist/infrastructure/transport-artifact-store.types.js.map +1 -0
  189. package/dist/infrastructure/transport-checkpoint.d.ts +5 -0
  190. package/dist/infrastructure/transport-checkpoint.js +105 -0
  191. package/dist/infrastructure/transport-checkpoint.js.map +1 -0
  192. package/dist/infrastructure/transport-checkpoint.types.d.ts +9 -0
  193. package/dist/infrastructure/transport-checkpoint.types.js +2 -0
  194. package/dist/infrastructure/transport-checkpoint.types.js.map +1 -0
  195. package/dist/infrastructure/transport-conversations.d.ts +4 -0
  196. package/dist/infrastructure/transport-conversations.js +69 -0
  197. package/dist/infrastructure/transport-conversations.js.map +1 -0
  198. package/dist/infrastructure/transport-conversations.types.d.ts +4 -0
  199. package/dist/infrastructure/transport-conversations.types.js +2 -0
  200. package/dist/infrastructure/transport-conversations.types.js.map +1 -0
  201. package/dist/infrastructure/transport-envelope.d.ts +7 -0
  202. package/dist/infrastructure/transport-envelope.js +47 -0
  203. package/dist/infrastructure/transport-envelope.js.map +1 -0
  204. package/dist/infrastructure/transport-journal.d.ts +6 -0
  205. package/dist/infrastructure/transport-journal.js +93 -0
  206. package/dist/infrastructure/transport-journal.js.map +1 -0
  207. package/dist/infrastructure/transport-journal.types.d.ts +10 -0
  208. package/dist/infrastructure/transport-journal.types.js +2 -0
  209. package/dist/infrastructure/transport-journal.types.js.map +1 -0
  210. package/dist/infrastructure/transport-json.d.ts +6 -0
  211. package/dist/infrastructure/transport-json.js +46 -0
  212. package/dist/infrastructure/transport-json.js.map +1 -0
  213. package/dist/infrastructure/transport-reservations.d.ts +5 -0
  214. package/dist/infrastructure/transport-reservations.js +89 -0
  215. package/dist/infrastructure/transport-reservations.js.map +1 -0
  216. package/dist/infrastructure/transport-reservations.types.d.ts +4 -0
  217. package/dist/infrastructure/transport-reservations.types.js +2 -0
  218. package/dist/infrastructure/transport-reservations.types.js.map +1 -0
  219. package/dist/infrastructure/transport.constants.d.ts +8 -0
  220. package/dist/infrastructure/transport.constants.js +9 -0
  221. package/dist/infrastructure/transport.constants.js.map +1 -0
  222. package/dist/infrastructure/workflow-checkpoint.js +7 -0
  223. package/dist/infrastructure/workflow-checkpoint.js.map +1 -1
  224. package/dist/infrastructure/workflow-checkpoint.types.d.ts +3 -1
  225. package/dist/infrastructure/workflow-checkpoint.types.js.map +1 -1
  226. package/dist/providers/agents.d.ts +1 -1
  227. package/dist/providers/agents.js.map +1 -1
  228. package/dist/providers/daytona-command.constants.d.ts +2 -0
  229. package/dist/providers/daytona-command.constants.js +13 -0
  230. package/dist/providers/daytona-command.constants.js.map +1 -0
  231. package/dist/providers/daytona-command.js +9 -3
  232. package/dist/providers/daytona-command.js.map +1 -1
  233. package/dist/providers/daytona-output.d.ts +4 -0
  234. package/dist/providers/daytona-output.js +37 -0
  235. package/dist/providers/daytona-output.js.map +1 -0
  236. package/dist/providers/daytona-terminal.js +3 -1
  237. package/dist/providers/daytona-terminal.js.map +1 -1
  238. package/dist/providers/vercel-command.js +2 -2
  239. package/dist/providers/vercel-command.js.map +1 -1
  240. package/package.json +31 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.2.0
4
+
5
+ - Add experimental `openaiCompatible()` and typed model-provider contracts for direct, non-streaming Chat Completions or Responses calls without Codex. Bound requests and response sizes, support cancellation and optional reported usage, and reject incomplete or unsupported outputs. Tool execution and the agent harness remain planned for phase two; live service compatibility is unvalidated.
6
+ - Add a bilingual Model providers reference family with experimental icons and generated warning banners explaining the limitations. Add the same warning treatment to Firecracker and FirecrackerOptions.
7
+ - Add local/S3 object transports for artifacts, exclusively owned checkpoints, segmented journals, native conversation snapshots, verified recovery archives, shared storage reservations, resource activity and remote inventory/retention/quota operations. Preserve existing directory stores and local execution paths; AWS SDK loading remains optional. Remote ownership recovery is explicit and authenticated live storage campaigns remain outstanding.
8
+ - Add the optional `@elie-laloum/outpost/queues/bullmq` adapter for standalone Redis and BullMQ 5, preserving queue identity, lossless JSON results, fenced leases, cancellation and deadlines. Include asynchronous connection cleanup, real Redis integration tests and bilingual reference and operational guidance.
9
+
10
+ - Rebuild the bilingual documentation with separate Guide and Reference navigation, family overviews, contextual API descriptions, preserved symbol URLs and redirects for moved guides. Add complete runnable preparations and a schema-validation guide covering Zod, Valibot and JSON Schema.
11
+ - Group reference families under five fixed sections with short English labels in both locales, localized overview links with house icons, and keyboard-accessible desktop/mobile navigation. Group egress policies with sandbox providers.
12
+ - Avoid recreating filesystem roots when initializing local transports on Windows, and accept native short/case aliases during inspection without allowing symlink traversal. Preserve child transcripts when capturing Windows conversations, verify relocated paths as decoded JSON, and build the library before running documented Docker/Podman examples in CI.
13
+
14
+ ## 4.1.0
15
+
16
+ - Replace global CLI parsing and manual help with Commander subcommands and command-specific options; use Clack selection prompts for interactive initialization. Previously ignored options belonging to other commands are now rejected. Headless and JSON output remain plain.
17
+ - Guide initialization through package manager and authentication choices. Check a requested package manager before writing files when `--install` is used. Build Docker/Podman images automatically; use `--no-build` to generate files without building. Existing generated workflows are unchanged.
18
+ - Generate explicit API-key, Claude subscription-token and Codex account-login setup. Validate required model credentials before allocation; prepare Codex API login through stdin, or copy an explicitly selected account credential seed into the private sandbox home. Host keychains are not exported.
19
+ - Add `CodexModelProvider` with a custom Responses API URL and API-key environment variable; expose `--base-url`, `--api-key-env` and `--model` in initialization. Chat Completions-only endpoints are not supported. Verify the native Codex connection against a local simulated Responses endpoint in container CI.
20
+ - Preserve Vercel allocation variables on every command, with command-level overrides. Regress generated Claude token forwarding and document that switching providers must preserve explicit model variables.
21
+ - Add manually enabled authenticated Claude/Codex cloud campaigns with sanitized reports. Missing credentials remain skipped; adding this fixture does not establish a successful live account campaign. Scheduled runs do not make model calls.
22
+ - Preserve Daytona’s session shell and real exit status for non-interactive commands; accept an already removed PTY during cancellation cleanup. Regress both defects found by live testing. Validate Claude OAuth workflows on Docker, Vercel and Daytona, plus Daytona terminal input, resize, exit, cancellation and reuse.
23
+ - Preserve exact Daytona command output through encoded streaming, fixing synchronization of Git paths delimited by NUL bytes. Verify unterminated output, Unicode, exit status, cancellation and warm reuse.
24
+ - Make unattended Gemini `yolo` requests trust the selected workspace with `--skip-trust`; preserve other approval modes and document the trust boundary. Clarify generated Codex subscription login for local and cloud providers.
25
+ - Validate authenticated file-editing workflows with Codex subscription login and Gemini API keys on Docker, Vercel and Daytona, a custom Responses endpoint on Docker and Vercel, and OpenRouter Responses on Daytona. The same unmodified adapter completes the OpenRouter model/tool/synchronization workflow; the other custom endpoint still resets HTTPS connections from Daytona before authentication. Codex API-key authentication was not exercised in this campaign.
26
+ - Refocus the bilingual roadmap on near-term reliability and medium-term directions with explicit validation criteria.
27
+
3
28
  ## 4.0.0
4
29
 
5
30
  This major release extends public workflow status unions. Exhaustive consumers must handle task statuses `paused` and `rejected`, and workflow result status `paused`. Research features remain explicitly opt-in with their documented limitations.
package/README.md CHANGED
@@ -4,16 +4,18 @@
4
4
 
5
5
  Outpost is a TypeScript library for running coding agents in reusable sandboxes, managing their Git workspaces and composing typed workflows. Claude Code, Codex and Gemini CLI adapters work with Docker, Podman, Vercel, Daytona or explicit host execution. Gemini supports fresh sessions only; native conversation capture, resume and fork are available for Claude Code and Codex.
6
6
 
7
- Version 4.0.0 adds durable orchestration, recovery and transfer improvements, Gemini CLI and native Daytona terminals. Research prototypes remain opt-in; see the [changelog](CHANGELOG.md#400) for public contract changes and the [roadmap](https://elie-laloum.github.io/outpost/project/roadmap/) for capabilities, limits and remaining work.
7
+ Version 4.2.0 adds local/S3 storage transports, an optional BullMQ/Redis task queue and experimental direct text model calls. The bilingual documentation now separates practical guides from the API reference. Direct model calls do not yet execute tools or edit repositories. See the [changelog](CHANGELOG.md#420) for details and the [roadmap](https://elie-laloum.github.io/outpost/project/roadmap/) for remaining validation and upcoming priorities.
8
8
 
9
9
  ## Get started
10
10
 
11
+ New to Outpost? Follow the [complete first-run workshop](https://elie-laloum.github.io/outpost/guide/start/quickstart/): choose Codex or Claude and account or API-key access, create a disposable TypeScript repository, then fix and verify a real test. The [Guide](https://elie-laloum.github.io/outpost/guide/) teaches the concepts; the [Reference](https://elie-laloum.github.io/outpost/reference/) explains exact contracts. Every [cookbook recipe](https://elie-laloum.github.io/outpost/guide/cookbook/) includes its own preparation and runnable code.
12
+
11
13
  Requires Node.js **24+**, Git, a target repository with a commit, and the credentials of your chosen agent. The workflow can live in its own directory. This example uses Docker.
12
14
 
13
15
  ```sh
14
16
  mkdir workflow1
15
17
  cd workflow1
16
- npx @elie-laloum/outpost init --yes --repository /path1/repository --install --build
18
+ npx @elie-laloum/outpost init --yes --repository /path1/repository --install
17
19
  ```
18
20
 
19
21
  Copy `.env.example` to `.env` and declare `OPENAI_API_KEY`. An empty declaration inherits the matching process variable. Then run the generated script:
@@ -22,12 +24,14 @@ Copy `.env.example` to `.env` and declare `OPENAI_API_KEY`. An empty declaration
22
24
  node run.ts "Add validation, run tests and commit the change"
23
25
  ```
24
26
 
25
- `init` creates `package.json`, `run.ts`, `brief.md`, `.env.example`, `.gitignore` and a container recipe directly in the workflow directory. Existing package manifests are preserved; explicit CommonJS projects get `run.mts` for compatibility. The script resolves its brief, environment and relative repository paths from its own directory. See [multi-repository workflows](https://elie-laloum.github.io/outpost/workflows/sandbox-tasks/) to orchestrate several repositories.
27
+ `init` creates `package.json`, `run.ts`, `brief.md`, `.env.example`, `.gitignore` and a container recipe directly in the workflow directory. Existing package manifests are preserved; explicit CommonJS projects get `run.mts` for compatibility. The script resolves its brief, environment and relative repository paths from its own directory. See [multi-repository workflows](https://elie-laloum.github.io/outpost/guide/workflows/sandbox-tasks/) to orchestrate several repositories.
26
28
 
27
- For Codex API-key authentication, add the login hook from [Connect Codex](https://elie-laloum.github.io/outpost/agents/connect-codex/) before dispatching.
29
+ Generated Codex API-key workflows prepare login inside the sandbox. For account login and credential storage, see [Connect Codex](https://elie-laloum.github.io/outpost/guide/agents/connect-codex/) before dispatching.
28
30
 
29
31
  ## Use the library
30
32
 
33
+ After preparing the selected agent credentials and provider, library calls follow this shape. For a fully runnable version with explicit authentication, use the [dispatch workshop](https://elie-laloum.github.io/outpost/guide/agents/dispatch/).
34
+
31
35
  ```ts
32
36
  import { dispatch, codex } from "@elie-laloum/outpost";
33
37
 
@@ -40,9 +44,9 @@ const result = await dispatch({
40
44
  console.log(result.branch, result.commits);
41
45
  ```
42
46
 
43
- `repository` selects a local Git checkout; without it, library calls use the current working directory. See [repository paths](https://elie-laloum.github.io/outpost/sandboxes/repositories/) for external checkouts and paths relative to the workflow script.
47
+ `repository` selects a local Git checkout; without it, library calls use the current working directory. See [repository paths](https://elie-laloum.github.io/outpost/guide/environment/repositories/) for external checkouts and paths relative to the workflow script.
44
48
 
45
- Learn about [sandboxes](https://elie-laloum.github.io/outpost/sandboxes/lifecycle/), [workflows](https://elie-laloum.github.io/outpost/workflows/graph/), [providers](https://elie-laloum.github.io/outpost/providers/overview/) and [recovery](https://elie-laloum.github.io/outpost/operations/recovery/) in the English/French documentation. The [roadmap](https://elie-laloum.github.io/outpost/project/roadmap/) describes future work.
49
+ Learn about [sandboxes](https://elie-laloum.github.io/outpost/guide/environment/lifecycle/), [workflows](https://elie-laloum.github.io/outpost/guide/workflows/graph/), [providers](https://elie-laloum.github.io/outpost/guide/environment/providers/overview/) and [recovery](https://elie-laloum.github.io/outpost/guide/operations/recovery/) in the English/French documentation. The [roadmap](https://elie-laloum.github.io/outpost/project/roadmap/) describes future work.
46
50
 
47
51
  ## Development
48
52
 
package/SECURITY.md CHANGED
@@ -15,3 +15,5 @@ Prompt command expansion only recognizes commands present in the original prompt
15
15
  Conversation transcripts, logs, bundles and patches may contain secrets. Runtime files are ignored by Git and sensitive files are created with restrictive permissions where supported. Keep API keys in environment variables or ignored `.env` files. Never embed tokens in tracked configuration, remote URLs or examples.
16
16
 
17
17
  Report vulnerabilities privately using the repository's security reporting channel when enabled, or contact the maintainer through the hosting profile. Do not include live credentials in reports. V1 receives fixes for reproducible security defects.
18
+
19
+ Explicit storage transports can send artifacts, journals, transcripts, recovery payloads and operational metadata to the configured object store. The caller owns the S3 client and credentials; they are not forwarded to agents. Use a dedicated private bucket prefix and access policy. Object revisions fence cooperating writers but do not authenticate artifact producers or operators. Stop an abandoned runner independently before releasing its checkpoint ownership; a remote PID is not proof of inactivity.
@@ -1,6 +1,8 @@
1
+ import { codexProvider } from "./codex-provider.js";
1
2
  import { codexEvents } from "./codex-events.js";
2
3
  import { codexRequest } from "./codex-request.js";
3
4
  export function codex(settings = {}) {
5
+ codexProvider(settings);
4
6
  return Object.freeze({
5
7
  name: "codex",
6
8
  conversations: "codex",
@@ -1 +1 @@
1
- {"version":3,"file":"codex-adapter.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,UAAU,KAAK,CAAC,WAA0B,EAAE;IAChD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,OAAO;QACb,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,QAAQ,CAAC,iBAAiB,IAAI,IAAI;QAC3C,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;QACnD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC;QACjD,MAAM,EAAE,WAAW;KACG,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["import type { AgentAdapter } from \"../../domain/agent.types.ts\";\nimport { codexEvents } from \"./codex-events.ts\";\nimport { codexRequest } from \"./codex-request.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codex(settings: CodexSettings = {}): AgentAdapter {\n return Object.freeze({\n name: \"codex\",\n conversations: \"codex\",\n resumable: true,\n capture: settings.saveConversations ?? true,\n variables: Object.freeze({ ...settings.variables }),\n request: (input) => codexRequest(settings, input),\n events: codexEvents,\n } satisfies AgentAdapter);\n}\n"]}
1
+ {"version":3,"file":"codex-adapter.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,UAAU,KAAK,CAAC,WAA0B,EAAE;IAChD,aAAa,CAAC,QAAQ,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,OAAO;QACb,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,QAAQ,CAAC,iBAAiB,IAAI,IAAI;QAC3C,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;QACnD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC;QACjD,MAAM,EAAE,WAAW;KACG,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["import type { AgentAdapter } from \"../../domain/agent.types.ts\";\nimport { codexProvider } from \"./codex-provider.ts\";\nimport { codexEvents } from \"./codex-events.ts\";\nimport { codexRequest } from \"./codex-request.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codex(settings: CodexSettings = {}): AgentAdapter {\n codexProvider(settings);\n return Object.freeze({\n name: \"codex\",\n conversations: \"codex\",\n resumable: true,\n capture: settings.saveConversations ?? true,\n variables: Object.freeze({ ...settings.variables }),\n request: (input) => codexRequest(settings, input),\n events: codexEvents,\n } satisfies AgentAdapter);\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { CodexSettings } from "./settings.types.ts";
2
+ export declare function codexProvider(settings: CodexSettings): readonly string[];
@@ -0,0 +1,32 @@
1
+ import { invariant } from "../../domain/errors.js";
2
+ export function codexProvider(settings) {
3
+ const provider = settings.modelProvider;
4
+ if (!provider)
5
+ return [];
6
+ invariant(settings.model?.trim(), "A custom model provider requires a model name");
7
+ let url;
8
+ try {
9
+ url = new URL(provider.baseUrl);
10
+ }
11
+ catch {
12
+ throw new Error("Model provider baseUrl must be an absolute HTTP(S) URL");
13
+ }
14
+ invariant(["http:", "https:"].includes(url.protocol) &&
15
+ !url.username &&
16
+ !url.password &&
17
+ !url.search &&
18
+ !url.hash, "Model provider baseUrl must use HTTP(S) without embedded credentials, query or fragment");
19
+ const environment = provider.apiKeyEnvironment ?? "OPENAI_API_KEY";
20
+ invariant(environment === false || /^[A-Za-z_][A-Za-z0-9_]*$/.test(environment), "Invalid model provider API-key environment variable");
21
+ const configuration = [
22
+ 'model_provider="outpost_compatible"',
23
+ 'model_providers.outpost_compatible.name="OpenAI compatible"',
24
+ `model_providers.outpost_compatible.base_url=${JSON.stringify(provider.baseUrl)}`,
25
+ 'model_providers.outpost_compatible.wire_api="responses"',
26
+ "model_providers.outpost_compatible.requires_openai_auth=false",
27
+ ];
28
+ if (environment !== false)
29
+ configuration.push(`model_providers.outpost_compatible.env_key=${JSON.stringify(environment)}`);
30
+ return configuration.flatMap((value) => ["-c", value]);
31
+ }
32
+ //# sourceMappingURL=codex-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-provider.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,MAAM,UAAU,aAAa,CAAC,QAAuB;IACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC;IACxC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,SAAS,CACP,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,EACtB,+CAA+C,CAChD,CAAC;IACF,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,SAAS,CACP,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;QACxC,CAAC,GAAG,CAAC,QAAQ;QACb,CAAC,GAAG,CAAC,QAAQ;QACb,CAAC,GAAG,CAAC,MAAM;QACX,CAAC,GAAG,CAAC,IAAI,EACX,yFAAyF,CAC1F,CAAC;IACF,MAAM,WAAW,GAAG,QAAQ,CAAC,iBAAiB,IAAI,gBAAgB,CAAC;IACnE,SAAS,CACP,WAAW,KAAK,KAAK,IAAI,0BAA0B,CAAC,IAAI,CAAC,WAAW,CAAC,EACrE,qDAAqD,CACtD,CAAC;IACF,MAAM,aAAa,GAAG;QACpB,qCAAqC;QACrC,6DAA6D;QAC7D,+CAA+C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjF,yDAAyD;QACzD,+DAA+D;KAChE,CAAC;IACF,IAAI,WAAW,KAAK,KAAK;QACvB,aAAa,CAAC,IAAI,CAChB,8CAA8C,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAC5E,CAAC;IACJ,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AACzD,CAAC","sourcesContent":["import { invariant } from \"../../domain/errors.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codexProvider(settings: CodexSettings): readonly string[] {\n const provider = settings.modelProvider;\n if (!provider) return [];\n invariant(\n settings.model?.trim(),\n \"A custom model provider requires a model name\",\n );\n let url: URL;\n try {\n url = new URL(provider.baseUrl);\n } catch {\n throw new Error(\"Model provider baseUrl must be an absolute HTTP(S) URL\");\n }\n invariant(\n [\"http:\", \"https:\"].includes(url.protocol) &&\n !url.username &&\n !url.password &&\n !url.search &&\n !url.hash,\n \"Model provider baseUrl must use HTTP(S) without embedded credentials, query or fragment\",\n );\n const environment = provider.apiKeyEnvironment ?? \"OPENAI_API_KEY\";\n invariant(\n environment === false || /^[A-Za-z_][A-Za-z0-9_]*$/.test(environment),\n \"Invalid model provider API-key environment variable\",\n );\n const configuration = [\n 'model_provider=\"outpost_compatible\"',\n 'model_providers.outpost_compatible.name=\"OpenAI compatible\"',\n `model_providers.outpost_compatible.base_url=${JSON.stringify(provider.baseUrl)}`,\n 'model_providers.outpost_compatible.wire_api=\"responses\"',\n \"model_providers.outpost_compatible.requires_openai_auth=false\",\n ];\n if (environment !== false)\n configuration.push(\n `model_providers.outpost_compatible.env_key=${JSON.stringify(environment)}`,\n );\n return configuration.flatMap((value) => [\"-c\", value]);\n}\n"]}
@@ -1,7 +1,8 @@
1
+ import { codexProvider } from "./codex-provider.js";
1
2
  import { validateContinuation } from "./continuation.js";
2
3
  export function codexRequest(settings, input) {
3
4
  validateContinuation(input);
4
- const args = [];
5
+ const args = [...codexProvider(settings)];
5
6
  if (settings.approvalReviewer === "auto_review")
6
7
  args.push("-a", "on-request", "-s", "danger-full-access", "-c", 'approvals_reviewer="auto_review"');
7
8
  if (settings.approvalReviewer !== "auto_review" && !input.interactive)
@@ -1 +1 @@
1
- {"version":3,"file":"codex-request.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,MAAM,UAAU,YAAY,CAC1B,QAAuB,EACvB,KAAiB;IAEjB,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,QAAQ,CAAC,gBAAgB,KAAK,aAAa;QAC7C,IAAI,CAAC,IAAI,CACP,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,oBAAoB,EACpB,IAAI,EACJ,kCAAkC,CACnC,CAAC;IACJ,IAAI,QAAQ,CAAC,gBAAgB,KAAK,aAAa,IAAI,CAAC,KAAK,CAAC,WAAW;QACnE,IAAI,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAC1D,IAAI,QAAQ,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,QAAQ,CAAC,SAAS;QACpB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,2BAA2B,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;IACpE,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,KAAK,CAAC,YAAY;QACpB,IAAI,CAAC,IAAI,CACP,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAC3C,KAAK,CAAC,YAAY,CAAC,EAAE,CACtB,CAAC;IACJ,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO;QACL,UAAU,EAAE,OAAO;QACnB,SAAS,EAAE,IAAI;QACf,GAAG,CAAC,KAAK,CAAC,WAAW;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInput } from \"../../domain/agent.types.ts\";\nimport type { Command } from \"../../domain/command.types.ts\";\nimport { validateContinuation } from \"./continuation.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codexRequest(\n settings: CodexSettings,\n input: AgentInput,\n): Command {\n validateContinuation(input);\n const args: string[] = [];\n if (settings.approvalReviewer === \"auto_review\")\n args.push(\n \"-a\",\n \"on-request\",\n \"-s\",\n \"danger-full-access\",\n \"-c\",\n 'approvals_reviewer=\"auto_review\"',\n );\n if (settings.approvalReviewer !== \"auto_review\" && !input.interactive)\n args.push(\"--dangerously-bypass-approvals-and-sandbox\");\n if (settings.model) args.push(\"--model\", settings.model);\n if (settings.reasoning)\n args.push(\"-c\", `model_reasoning_effort=\"${settings.reasoning}\"`);\n if (!input.interactive) args.push(\"exec\");\n if (input.continuation)\n args.push(\n input.continuation.fork ? \"fork\" : \"resume\",\n input.continuation.id,\n );\n if (!input.interactive) args.push(\"--json\", \"-\");\n if (input.interactive && input.text !== undefined)\n args.push(\"--\", input.text);\n return {\n executable: \"codex\",\n arguments: args,\n ...(input.interactive\n ? { interactive: true }\n : { stdin: input.text ?? \"\" }),\n };\n}\n"]}
1
+ {"version":3,"file":"codex-request.js","sourceRoot":"","sources":["../../../src/adapters/agents/codex-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,MAAM,UAAU,YAAY,CAC1B,QAAuB,EACvB,KAAiB;IAEjB,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAa,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpD,IAAI,QAAQ,CAAC,gBAAgB,KAAK,aAAa;QAC7C,IAAI,CAAC,IAAI,CACP,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,oBAAoB,EACpB,IAAI,EACJ,kCAAkC,CACnC,CAAC;IACJ,IAAI,QAAQ,CAAC,gBAAgB,KAAK,aAAa,IAAI,CAAC,KAAK,CAAC,WAAW;QACnE,IAAI,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAC1D,IAAI,QAAQ,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,QAAQ,CAAC,SAAS;QACpB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,2BAA2B,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;IACpE,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,KAAK,CAAC,YAAY;QACpB,IAAI,CAAC,IAAI,CACP,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAC3C,KAAK,CAAC,YAAY,CAAC,EAAE,CACtB,CAAC;IACJ,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO;QACL,UAAU,EAAE,OAAO;QACnB,SAAS,EAAE,IAAI;QACf,GAAG,CAAC,KAAK,CAAC,WAAW;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInput } from \"../../domain/agent.types.ts\";\nimport type { Command } from \"../../domain/command.types.ts\";\nimport { codexProvider } from \"./codex-provider.ts\";\nimport { validateContinuation } from \"./continuation.ts\";\nimport type { CodexSettings } from \"./settings.types.ts\";\n\nexport function codexRequest(\n settings: CodexSettings,\n input: AgentInput,\n): Command {\n validateContinuation(input);\n const args: string[] = [...codexProvider(settings)];\n if (settings.approvalReviewer === \"auto_review\")\n args.push(\n \"-a\",\n \"on-request\",\n \"-s\",\n \"danger-full-access\",\n \"-c\",\n 'approvals_reviewer=\"auto_review\"',\n );\n if (settings.approvalReviewer !== \"auto_review\" && !input.interactive)\n args.push(\"--dangerously-bypass-approvals-and-sandbox\");\n if (settings.model) args.push(\"--model\", settings.model);\n if (settings.reasoning)\n args.push(\"-c\", `model_reasoning_effort=\"${settings.reasoning}\"`);\n if (!input.interactive) args.push(\"exec\");\n if (input.continuation)\n args.push(\n input.continuation.fork ? \"fork\" : \"resume\",\n input.continuation.id,\n );\n if (!input.interactive) args.push(\"--json\", \"-\");\n if (input.interactive && input.text !== undefined)\n args.push(\"--\", input.text);\n return {\n executable: \"codex\",\n arguments: args,\n ...(input.interactive\n ? { interactive: true }\n : { stdin: input.text ?? \"\" }),\n };\n}\n"]}
@@ -6,6 +6,8 @@ export function geminiRequest(settings, input) {
6
6
  args.push("--model", settings.model);
7
7
  const approvalMode = settings.approvalMode ?? (input.interactive ? "default" : "yolo");
8
8
  args.push("--approval-mode", approvalMode);
9
+ if (!input.interactive && approvalMode === "yolo")
10
+ args.push("--skip-trust");
9
11
  if (!input.interactive)
10
12
  args.push("--output-format", "stream-json");
11
13
  if (input.interactive && input.text !== undefined)
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-request.js","sourceRoot":"","sources":["../../../src/adapters/agents/gemini-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,MAAM,UAAU,aAAa,CAC3B,QAAwB,EACxB,KAAiB;IAEjB,SAAS,CACP,CAAC,KAAK,CAAC,YAAY,EACnB,yDAAyD,CAC1D,CAAC;IACF,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,QAAQ,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,MAAM,YAAY,GAChB,QAAQ,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACpE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IACpE,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC/C,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO;QACL,UAAU,EAAE,QAAQ;QACpB,SAAS,EAAE,IAAI;QACf,GAAG,CAAC,KAAK,CAAC,WAAW;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInput } from \"../../domain/agent.types.ts\";\nimport type { Command } from \"../../domain/command.types.ts\";\nimport { invariant } from \"../../domain/errors.ts\";\nimport type { GeminiSettings } from \"./gemini.types.ts\";\n\nexport function geminiRequest(\n settings: GeminiSettings,\n input: AgentInput,\n): Command {\n invariant(\n !input.continuation,\n \"Gemini does not support continuation or fork in Outpost\",\n );\n const args: string[] = [];\n if (settings.model) args.push(\"--model\", settings.model);\n const approvalMode =\n settings.approvalMode ?? (input.interactive ? \"default\" : \"yolo\");\n args.push(\"--approval-mode\", approvalMode);\n if (!input.interactive) args.push(\"--output-format\", \"stream-json\");\n if (input.interactive && input.text !== undefined)\n args.push(\"--prompt-interactive\", input.text);\n return {\n executable: \"gemini\",\n arguments: args,\n ...(input.interactive\n ? { interactive: true }\n : { stdin: input.text ?? \"\" }),\n };\n}\n"]}
1
+ {"version":3,"file":"gemini-request.js","sourceRoot":"","sources":["../../../src/adapters/agents/gemini-request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,MAAM,UAAU,aAAa,CAC3B,QAAwB,EACxB,KAAiB;IAEjB,SAAS,CACP,CAAC,KAAK,CAAC,YAAY,EACnB,yDAAyD,CAC1D,CAAC;IACF,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,QAAQ,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,MAAM,YAAY,GAChB,QAAQ,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACpE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,YAAY,KAAK,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7E,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IACpE,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAC/C,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO;QACL,UAAU,EAAE,QAAQ;QACpB,SAAS,EAAE,IAAI;QACf,GAAG,CAAC,KAAK,CAAC,WAAW;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentInput } from \"../../domain/agent.types.ts\";\nimport type { Command } from \"../../domain/command.types.ts\";\nimport { invariant } from \"../../domain/errors.ts\";\nimport type { GeminiSettings } from \"./gemini.types.ts\";\n\nexport function geminiRequest(\n settings: GeminiSettings,\n input: AgentInput,\n): Command {\n invariant(\n !input.continuation,\n \"Gemini does not support continuation or fork in Outpost\",\n );\n const args: string[] = [];\n if (settings.model) args.push(\"--model\", settings.model);\n const approvalMode =\n settings.approvalMode ?? (input.interactive ? \"default\" : \"yolo\");\n args.push(\"--approval-mode\", approvalMode);\n if (!input.interactive && approvalMode === \"yolo\") args.push(\"--skip-trust\");\n if (!input.interactive) args.push(\"--output-format\", \"stream-json\");\n if (input.interactive && input.text !== undefined)\n args.push(\"--prompt-interactive\", input.text);\n return {\n executable: \"gemini\",\n arguments: args,\n ...(input.interactive\n ? { interactive: true }\n : { stdin: input.text ?? \"\" }),\n };\n}\n"]}
@@ -8,7 +8,12 @@ export interface ClaudeSettings extends CommonAgentSettings {
8
8
  readonly reasoning?: "low" | "medium" | "high" | "xhigh" | "max";
9
9
  readonly permissions?: "default" | "acceptEdits" | "plan" | "auto" | "dontAsk" | "bypassPermissions";
10
10
  }
11
+ export interface CodexModelProvider {
12
+ readonly baseUrl: string;
13
+ readonly apiKeyEnvironment?: string | false;
14
+ }
11
15
  export interface CodexSettings extends CommonAgentSettings {
16
+ readonly modelProvider?: CodexModelProvider;
12
17
  readonly reasoning?: "low" | "medium" | "high" | "xhigh";
13
18
  readonly approvalReviewer?: "user" | "auto_review";
14
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"settings.types.js","sourceRoot":"","sources":["../../../src/adapters/agents/settings.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Variables } from \"../../domain/command.types.ts\";\n\nexport interface CommonAgentSettings {\n readonly model?: string;\n readonly variables?: Variables;\n readonly saveConversations?: boolean;\n}\n\nexport interface ClaudeSettings extends CommonAgentSettings {\n readonly reasoning?: \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n readonly permissions?:\n | \"default\"\n | \"acceptEdits\"\n | \"plan\"\n | \"auto\"\n | \"dontAsk\"\n | \"bypassPermissions\";\n}\n\nexport interface CodexSettings extends CommonAgentSettings {\n readonly reasoning?: \"low\" | \"medium\" | \"high\" | \"xhigh\";\n readonly approvalReviewer?: \"user\" | \"auto_review\";\n}\n"]}
1
+ {"version":3,"file":"settings.types.js","sourceRoot":"","sources":["../../../src/adapters/agents/settings.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Variables } from \"../../domain/command.types.ts\";\n\nexport interface CommonAgentSettings {\n readonly model?: string;\n readonly variables?: Variables;\n readonly saveConversations?: boolean;\n}\n\nexport interface ClaudeSettings extends CommonAgentSettings {\n readonly reasoning?: \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n readonly permissions?:\n | \"default\"\n | \"acceptEdits\"\n | \"plan\"\n | \"auto\"\n | \"dontAsk\"\n | \"bypassPermissions\";\n}\n\nexport interface CodexModelProvider {\n readonly baseUrl: string;\n readonly apiKeyEnvironment?: string | false;\n}\n\nexport interface CodexSettings extends CommonAgentSettings {\n readonly modelProvider?: CodexModelProvider;\n readonly reasoning?: \"low\" | \"medium\" | \"high\" | \"xhigh\";\n readonly approvalReviewer?: \"user\" | \"auto_review\";\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export declare const MODEL_TIMEOUT_MS = 120000;
2
+ export declare const MODEL_RESPONSE_BYTES: number;
3
+ export declare const MODEL_MAX_TIMEOUT_MS = 2147483647;
4
+ export declare const MODEL_REQUEST_FIELDS: Set<string>;
@@ -0,0 +1,10 @@
1
+ export const MODEL_TIMEOUT_MS = 120_000;
2
+ export const MODEL_RESPONSE_BYTES = 8 * 1024 * 1024;
3
+ export const MODEL_MAX_TIMEOUT_MS = 2_147_483_647;
4
+ export const MODEL_REQUEST_FIELDS = new Set([
5
+ "prompt",
6
+ "system",
7
+ "maxOutputTokens",
8
+ "signal",
9
+ ]);
10
+ //# sourceMappingURL=openai-compatible.constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible.constants.js","sourceRoot":"","sources":["../../../src/adapters/models/openai-compatible.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC;AACxC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AACpD,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAClD,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IAC1C,QAAQ;IACR,QAAQ;IACR,iBAAiB;IACjB,QAAQ;CACT,CAAC,CAAC","sourcesContent":["export const MODEL_TIMEOUT_MS = 120_000;\nexport const MODEL_RESPONSE_BYTES = 8 * 1024 * 1024;\nexport const MODEL_MAX_TIMEOUT_MS = 2_147_483_647;\nexport const MODEL_REQUEST_FIELDS = new Set([\n \"prompt\",\n \"system\",\n \"maxOutputTokens\",\n \"signal\",\n]);\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ModelProvider } from "../../domain/model.types.ts";
2
+ import type { OpenAICompatibleOptions } from "./openai-compatible.types.ts";
3
+ export declare function openaiCompatible(options: OpenAICompatibleOptions): ModelProvider;
@@ -0,0 +1,78 @@
1
+ import { invariant, OutpostError, positive } from "../../domain/errors.js";
2
+ import { MODEL_MAX_TIMEOUT_MS, MODEL_RESPONSE_BYTES, MODEL_TIMEOUT_MS, } from "./openai-compatible.constants.js";
3
+ import { modelProtocols, validateModelRequest } from "./openai-request.js";
4
+ import { modelJson } from "./openai-http.js";
5
+ export function openaiCompatible(options) {
6
+ let base;
7
+ try {
8
+ base = new URL(options.baseUrl);
9
+ }
10
+ catch {
11
+ throw new OutpostError("configuration", "Model baseUrl must be an absolute HTTP(S) URL");
12
+ }
13
+ invariant(["http:", "https:"].includes(base.protocol) &&
14
+ !base.username &&
15
+ !base.password &&
16
+ !base.search &&
17
+ !base.hash, "Model baseUrl must use HTTP(S) without credentials, query or fragment");
18
+ invariant(typeof options.model === "string" && options.model.trim(), "Model name must be nonempty text");
19
+ invariant(options.apiKey === false ||
20
+ (typeof options.apiKey === "string" &&
21
+ options.apiKey.trim() &&
22
+ !/[\r\n]/.test(options.apiKey)), "Model apiKey must be a nonempty key or false for unauthenticated endpoints");
23
+ const api = options.api ?? "chat-completions";
24
+ invariant(Object.hasOwn(modelProtocols, api), "Unsupported model API protocol");
25
+ const protocol = modelProtocols[api];
26
+ const endpoint = `${base.href.replace(/\/+$/, "")}/${protocol.path}`;
27
+ const timeoutMs = positive(options.timeoutMs ?? MODEL_TIMEOUT_MS, "Model timeoutMs");
28
+ invariant(timeoutMs <= MODEL_MAX_TIMEOUT_MS, "Model timeoutMs exceeds the supported timer range");
29
+ const maxBytes = positive(options.maxResponseBytes ?? MODEL_RESPONSE_BYTES, "Model maxResponseBytes");
30
+ const model = options.model;
31
+ const apiKey = options.apiKey;
32
+ return Object.freeze({
33
+ name: "openai-compatible",
34
+ async generate(request) {
35
+ validateModelRequest(request);
36
+ const deadline = new AbortController();
37
+ const signal = request.signal
38
+ ? AbortSignal.any([request.signal, deadline.signal])
39
+ : deadline.signal;
40
+ const timer = setTimeout(() => deadline.abort(), timeoutMs);
41
+ try {
42
+ signal.throwIfAborted();
43
+ const response = await fetch(endpoint, {
44
+ method: "POST",
45
+ redirect: "error",
46
+ headers: {
47
+ "Content-Type": "application/json",
48
+ ...(apiKey === false ? {} : { Authorization: `Bearer ${apiKey}` }),
49
+ },
50
+ body: JSON.stringify(protocol.build(model, request)),
51
+ signal,
52
+ });
53
+ if (!response.ok) {
54
+ await response.body?.cancel();
55
+ throw new OutpostError("provider", `Model request failed with HTTP ${response.status}`, { status: response.status });
56
+ }
57
+ const value = await modelJson(response, maxBytes);
58
+ signal.throwIfAborted();
59
+ return protocol.read(value);
60
+ }
61
+ catch (error) {
62
+ if (signal.aborted) {
63
+ const timedOut = deadline.signal.aborted && !request.signal?.aborted;
64
+ throw new OutpostError(timedOut ? "timeout" : "aborted", timedOut
65
+ ? "Model request timed out"
66
+ : "Model request was cancelled");
67
+ }
68
+ if (error instanceof OutpostError)
69
+ throw error;
70
+ throw new OutpostError("provider", "Model request failed during HTTP transport");
71
+ }
72
+ finally {
73
+ clearTimeout(timer);
74
+ }
75
+ },
76
+ });
77
+ }
78
+ //# sourceMappingURL=openai-compatible.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible.js","sourceRoot":"","sources":["../../../src/adapters/models/openai-compatible.ts"],"names":[],"mappings":"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","sourcesContent":["import { invariant, OutpostError, positive } from \"../../domain/errors.ts\";\nimport type { ModelProvider, ModelRequest } from \"../../domain/model.types.ts\";\nimport type { OpenAICompatibleOptions } from \"./openai-compatible.types.ts\";\nimport {\n MODEL_MAX_TIMEOUT_MS,\n MODEL_RESPONSE_BYTES,\n MODEL_TIMEOUT_MS,\n} from \"./openai-compatible.constants.ts\";\nimport { modelProtocols, validateModelRequest } from \"./openai-request.ts\";\nimport { modelJson } from \"./openai-http.ts\";\n\nexport function openaiCompatible(\n options: OpenAICompatibleOptions,\n): ModelProvider {\n let base: URL;\n try {\n base = new URL(options.baseUrl);\n } catch {\n throw new OutpostError(\n \"configuration\",\n \"Model baseUrl must be an absolute HTTP(S) URL\",\n );\n }\n invariant(\n [\"http:\", \"https:\"].includes(base.protocol) &&\n !base.username &&\n !base.password &&\n !base.search &&\n !base.hash,\n \"Model baseUrl must use HTTP(S) without credentials, query or fragment\",\n );\n invariant(\n typeof options.model === \"string\" && options.model.trim(),\n \"Model name must be nonempty text\",\n );\n invariant(\n options.apiKey === false ||\n (typeof options.apiKey === \"string\" &&\n options.apiKey.trim() &&\n !/[\\r\\n]/.test(options.apiKey)),\n \"Model apiKey must be a nonempty key or false for unauthenticated endpoints\",\n );\n const api = options.api ?? \"chat-completions\";\n invariant(\n Object.hasOwn(modelProtocols, api),\n \"Unsupported model API protocol\",\n );\n const protocol = modelProtocols[api]!;\n const endpoint = `${base.href.replace(/\\/+$/, \"\")}/${protocol.path}`;\n const timeoutMs = positive(\n options.timeoutMs ?? MODEL_TIMEOUT_MS,\n \"Model timeoutMs\",\n );\n invariant(\n timeoutMs <= MODEL_MAX_TIMEOUT_MS,\n \"Model timeoutMs exceeds the supported timer range\",\n );\n const maxBytes = positive(\n options.maxResponseBytes ?? MODEL_RESPONSE_BYTES,\n \"Model maxResponseBytes\",\n );\n const model = options.model;\n const apiKey = options.apiKey;\n return Object.freeze({\n name: \"openai-compatible\",\n async generate(request: ModelRequest) {\n validateModelRequest(request);\n const deadline = new AbortController();\n const signal = request.signal\n ? AbortSignal.any([request.signal, deadline.signal])\n : deadline.signal;\n const timer = setTimeout(() => deadline.abort(), timeoutMs);\n try {\n signal.throwIfAborted();\n const response = await fetch(endpoint, {\n method: \"POST\",\n redirect: \"error\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(apiKey === false ? {} : { Authorization: `Bearer ${apiKey}` }),\n },\n body: JSON.stringify(protocol.build(model, request)),\n signal,\n });\n if (!response.ok) {\n await response.body?.cancel();\n throw new OutpostError(\n \"provider\",\n `Model request failed with HTTP ${response.status}`,\n { status: response.status },\n );\n }\n const value = await modelJson(response, maxBytes);\n signal.throwIfAborted();\n return protocol.read(value);\n } catch (error) {\n if (signal.aborted) {\n const timedOut = deadline.signal.aborted && !request.signal?.aborted;\n throw new OutpostError(\n timedOut ? \"timeout\" : \"aborted\",\n timedOut\n ? \"Model request timed out\"\n : \"Model request was cancelled\",\n );\n }\n if (error instanceof OutpostError) throw error;\n throw new OutpostError(\n \"provider\",\n \"Model request failed during HTTP transport\",\n );\n } finally {\n clearTimeout(timer);\n }\n },\n });\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ModelRequest, ModelResult } from "../../domain/model.types.ts";
2
+ export interface OpenAICompatibleOptions {
3
+ readonly baseUrl: string;
4
+ readonly model: string;
5
+ readonly apiKey: string | false;
6
+ readonly api?: "chat-completions" | "responses";
7
+ readonly timeoutMs?: number;
8
+ readonly maxResponseBytes?: number;
9
+ }
10
+ export interface ModelProtocol {
11
+ readonly path: string;
12
+ build(model: string, request: ModelRequest): Record<string, unknown>;
13
+ read(value: unknown): ModelResult;
14
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=openai-compatible.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible.types.js","sourceRoot":"","sources":["../../../src/adapters/models/openai-compatible.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ModelRequest, ModelResult } from \"../../domain/model.types.ts\";\n\nexport interface OpenAICompatibleOptions {\n readonly baseUrl: string;\n readonly model: string;\n readonly apiKey: string | false;\n readonly api?: \"chat-completions\" | \"responses\";\n readonly timeoutMs?: number;\n readonly maxResponseBytes?: number;\n}\n\nexport interface ModelProtocol {\n readonly path: string;\n build(model: string, request: ModelRequest): Record<string, unknown>;\n read(value: unknown): ModelResult;\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function modelJson(response: Response, maxBytes: number): Promise<unknown>;
@@ -0,0 +1,30 @@
1
+ import { OutpostError } from "../../domain/errors.js";
2
+ export async function modelJson(response, maxBytes) {
3
+ const reader = response.body?.getReader();
4
+ if (!reader)
5
+ throw new OutpostError("response", "Model response has no body");
6
+ const chunks = [];
7
+ let size = 0;
8
+ try {
9
+ for (;;) {
10
+ const { done, value } = await reader.read();
11
+ if (done)
12
+ break;
13
+ size += value.byteLength;
14
+ if (size > maxBytes)
15
+ throw new OutpostError("response", "Model response exceeds maxResponseBytes");
16
+ chunks.push(value);
17
+ }
18
+ }
19
+ finally {
20
+ await reader.cancel().catch(() => undefined);
21
+ reader.releaseLock();
22
+ }
23
+ try {
24
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
25
+ }
26
+ catch {
27
+ throw new OutpostError("response", "Model response is not valid JSON");
28
+ }
29
+ }
30
+ //# sourceMappingURL=openai-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-http.js","sourceRoot":"","sources":["../../../src/adapters/models/openai-http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAkB,EAClB,QAAgB;IAEhB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;IAC1C,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,4BAA4B,CAAC,CAAC;IAC9E,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC;YACzB,IAAI,IAAI,GAAG,QAAQ;gBACjB,MAAM,IAAI,YAAY,CACpB,UAAU,EACV,yCAAyC,CAC1C,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,kCAAkC,CAAC,CAAC;IACzE,CAAC;AACH,CAAC","sourcesContent":["import { OutpostError } from \"../../domain/errors.ts\";\n\nexport async function modelJson(\n response: Response,\n maxBytes: number,\n): Promise<unknown> {\n const reader = response.body?.getReader();\n if (!reader) throw new OutpostError(\"response\", \"Model response has no body\");\n const chunks: Uint8Array[] = [];\n let size = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > maxBytes)\n throw new OutpostError(\n \"response\",\n \"Model response exceeds maxResponseBytes\",\n );\n chunks.push(value);\n }\n } finally {\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n try {\n return JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n } catch {\n throw new OutpostError(\"response\", \"Model response is not valid JSON\");\n }\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { ModelRequest } from "../../domain/model.types.ts";
2
+ import type { ModelProtocol } from "./openai-compatible.types.ts";
3
+ export declare function validateModelRequest(request: ModelRequest): void;
4
+ export declare const modelProtocols: Readonly<Record<string, ModelProtocol>>;
@@ -0,0 +1,46 @@
1
+ import { invariant, positive } from "../../domain/errors.js";
2
+ import { MODEL_REQUEST_FIELDS } from "./openai-compatible.constants.js";
3
+ import { readChatCompletion, readModelResponse } from "./openai-response.js";
4
+ export function validateModelRequest(request) {
5
+ invariant(request !== null && typeof request === "object", "Model request must be an object");
6
+ invariant(Object.keys(request).every((key) => MODEL_REQUEST_FIELDS.has(key)), "Unsupported model request field; tools, streaming and conversations are not implemented");
7
+ invariant(typeof request.prompt === "string" && request.prompt.trim(), "Model prompt must be nonempty text");
8
+ invariant(request.system === undefined || typeof request.system === "string", "Model system instructions must be text");
9
+ if (request.maxOutputTokens !== undefined)
10
+ positive(request.maxOutputTokens, "Model maxOutputTokens");
11
+ }
12
+ export const modelProtocols = {
13
+ "chat-completions": {
14
+ path: "chat/completions",
15
+ build: (model, request) => ({
16
+ model,
17
+ messages: [
18
+ ...(request.system === undefined
19
+ ? []
20
+ : [{ role: "system", content: request.system }]),
21
+ { role: "user", content: request.prompt },
22
+ ],
23
+ stream: false,
24
+ store: false,
25
+ ...(request.maxOutputTokens === undefined
26
+ ? {}
27
+ : { max_completion_tokens: request.maxOutputTokens }),
28
+ }),
29
+ read: readChatCompletion,
30
+ },
31
+ responses: {
32
+ path: "responses",
33
+ build: (model, request) => ({
34
+ model,
35
+ input: request.prompt,
36
+ ...(request.system === undefined ? {} : { instructions: request.system }),
37
+ stream: false,
38
+ store: false,
39
+ ...(request.maxOutputTokens === undefined
40
+ ? {}
41
+ : { max_output_tokens: request.maxOutputTokens }),
42
+ }),
43
+ read: readModelResponse,
44
+ },
45
+ };
46
+ //# sourceMappingURL=openai-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-request.js","sourceRoot":"","sources":["../../../src/adapters/models/openai-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE7E,MAAM,UAAU,oBAAoB,CAAC,OAAqB;IACxD,SAAS,CACP,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAC/C,iCAAiC,CAClC,CAAC;IACF,SAAS,CACP,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAClE,yFAAyF,CAC1F,CAAC;IACF,SAAS,CACP,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAC3D,oCAAoC,CACrC,CAAC;IACF,SAAS,CACP,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,EAClE,wCAAwC,CACzC,CAAC;IACF,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS;QACvC,QAAQ,CAAC,OAAO,CAAC,eAAe,EAAE,uBAAuB,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAA4C;IACrE,kBAAkB,EAAE;QAClB,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;YAC1B,KAAK;YACL,QAAQ,EAAE;gBACR,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS;oBAC9B,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;gBAClD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE;aAC1C;YACD,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,KAAK;YACZ,GAAG,CAAC,OAAO,CAAC,eAAe,KAAK,SAAS;gBACvC,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,EAAE,qBAAqB,EAAE,OAAO,CAAC,eAAe,EAAE,CAAC;SACxD,CAAC;QACF,IAAI,EAAE,kBAAkB;KACzB;IACD,SAAS,EAAE;QACT,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;YAC1B,KAAK;YACL,KAAK,EAAE,OAAO,CAAC,MAAM;YACrB,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;YACzE,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,KAAK;YACZ,GAAG,CAAC,OAAO,CAAC,eAAe,KAAK,SAAS;gBACvC,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,eAAe,EAAE,CAAC;SACpD,CAAC;QACF,IAAI,EAAE,iBAAiB;KACxB;CACF,CAAC","sourcesContent":["import { invariant, positive } from \"../../domain/errors.ts\";\nimport type { ModelRequest } from \"../../domain/model.types.ts\";\nimport type { ModelProtocol } from \"./openai-compatible.types.ts\";\nimport { MODEL_REQUEST_FIELDS } from \"./openai-compatible.constants.ts\";\nimport { readChatCompletion, readModelResponse } from \"./openai-response.ts\";\n\nexport function validateModelRequest(request: ModelRequest): void {\n invariant(\n request !== null && typeof request === \"object\",\n \"Model request must be an object\",\n );\n invariant(\n Object.keys(request).every((key) => MODEL_REQUEST_FIELDS.has(key)),\n \"Unsupported model request field; tools, streaming and conversations are not implemented\",\n );\n invariant(\n typeof request.prompt === \"string\" && request.prompt.trim(),\n \"Model prompt must be nonempty text\",\n );\n invariant(\n request.system === undefined || typeof request.system === \"string\",\n \"Model system instructions must be text\",\n );\n if (request.maxOutputTokens !== undefined)\n positive(request.maxOutputTokens, \"Model maxOutputTokens\");\n}\n\nexport const modelProtocols: Readonly<Record<string, ModelProtocol>> = {\n \"chat-completions\": {\n path: \"chat/completions\",\n build: (model, request) => ({\n model,\n messages: [\n ...(request.system === undefined\n ? []\n : [{ role: \"system\", content: request.system }]),\n { role: \"user\", content: request.prompt },\n ],\n stream: false,\n store: false,\n ...(request.maxOutputTokens === undefined\n ? {}\n : { max_completion_tokens: request.maxOutputTokens }),\n }),\n read: readChatCompletion,\n },\n responses: {\n path: \"responses\",\n build: (model, request) => ({\n model,\n input: request.prompt,\n ...(request.system === undefined ? {} : { instructions: request.system }),\n stream: false,\n store: false,\n ...(request.maxOutputTokens === undefined\n ? {}\n : { max_output_tokens: request.maxOutputTokens }),\n }),\n read: readModelResponse,\n },\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ModelResult } from "../../domain/model.types.ts";
2
+ export declare function readChatCompletion(value: unknown): ModelResult;
3
+ export declare function readModelResponse(value: unknown): ModelResult;
@@ -0,0 +1,65 @@
1
+ import { OutpostError } from "../../domain/errors.js";
2
+ function requireResponse(condition) {
3
+ if (!condition)
4
+ throw new OutpostError("response", "Invalid or unsupported model response");
5
+ }
6
+ function object(value) {
7
+ requireResponse(value !== null && typeof value === "object" && !Array.isArray(value));
8
+ return value;
9
+ }
10
+ function tokens(value) {
11
+ requireResponse(typeof value === "number" && Number.isSafeInteger(value) && value >= 0);
12
+ return value;
13
+ }
14
+ function usage(value, input, output, details) {
15
+ if (value === undefined || value === null)
16
+ return undefined;
17
+ const data = object(value);
18
+ const cached = data[details] == null
19
+ ? 0
20
+ : tokens(object(data[details]).cached_tokens ?? 0);
21
+ const result = {
22
+ input: tokens(data[input]),
23
+ output: tokens(data[output]),
24
+ cached,
25
+ };
26
+ requireResponse(cached <= result.input);
27
+ return result;
28
+ }
29
+ export function readChatCompletion(value) {
30
+ const data = object(value);
31
+ requireResponse(Array.isArray(data.choices) && data.choices.length === 1);
32
+ const choice = object(data.choices[0]);
33
+ requireResponse(choice.finish_reason === "stop");
34
+ const message = object(choice.message);
35
+ requireResponse(message.role === "assistant" && typeof message.content === "string");
36
+ requireResponse(message.refusal == null && message.function_call == null);
37
+ requireResponse(message.tool_calls == null ||
38
+ (Array.isArray(message.tool_calls) && message.tool_calls.length === 0));
39
+ const reported = usage(data.usage, "prompt_tokens", "completion_tokens", "prompt_tokens_details");
40
+ return { text: message.content, ...(reported ? { usage: reported } : {}) };
41
+ }
42
+ export function readModelResponse(value) {
43
+ const data = object(value);
44
+ requireResponse(data.status === "completed" && data.error == null);
45
+ requireResponse(Array.isArray(data.output));
46
+ const texts = [];
47
+ for (const value of data.output) {
48
+ const item = object(value);
49
+ if (item.type === "reasoning")
50
+ continue;
51
+ requireResponse(item.type === "message" &&
52
+ item.role === "assistant" &&
53
+ item.status === "completed");
54
+ requireResponse(Array.isArray(item.content));
55
+ for (const value of item.content) {
56
+ const part = object(value);
57
+ requireResponse(part.type === "output_text" && typeof part.text === "string");
58
+ texts.push(part.text);
59
+ }
60
+ }
61
+ requireResponse(texts.length > 0);
62
+ const reported = usage(data.usage, "input_tokens", "output_tokens", "input_tokens_details");
63
+ return { text: texts.join(""), ...(reported ? { usage: reported } : {}) };
64
+ }
65
+ //# sourceMappingURL=openai-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-response.js","sourceRoot":"","sources":["../../../src/adapters/models/openai-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { OutpostError } from \"../../domain/errors.ts\";\nimport type { ModelResult } from \"../../domain/model.types.ts\";\nimport type { Usage } from \"../../domain/agent.types.ts\";\n\nfunction requireResponse(condition: unknown): asserts condition {\n if (!condition)\n throw new OutpostError(\"response\", \"Invalid or unsupported model response\");\n}\n\nfunction object(value: unknown): Record<string, unknown> {\n requireResponse(\n value !== null && typeof value === \"object\" && !Array.isArray(value),\n );\n return value as Record<string, unknown>;\n}\n\nfunction tokens(value: unknown): number {\n requireResponse(\n typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0,\n );\n return value;\n}\n\nfunction usage(\n value: unknown,\n input: string,\n output: string,\n details: string,\n): Usage | undefined {\n if (value === undefined || value === null) return undefined;\n const data = object(value);\n const cached =\n data[details] == null\n ? 0\n : tokens(object(data[details]).cached_tokens ?? 0);\n const result = {\n input: tokens(data[input]),\n output: tokens(data[output]),\n cached,\n };\n requireResponse(cached <= result.input);\n return result;\n}\n\nexport function readChatCompletion(value: unknown): ModelResult {\n const data = object(value);\n requireResponse(Array.isArray(data.choices) && data.choices.length === 1);\n const choice = object(data.choices[0]);\n requireResponse(choice.finish_reason === \"stop\");\n const message = object(choice.message);\n requireResponse(\n message.role === \"assistant\" && typeof message.content === \"string\",\n );\n requireResponse(message.refusal == null && message.function_call == null);\n requireResponse(\n message.tool_calls == null ||\n (Array.isArray(message.tool_calls) && message.tool_calls.length === 0),\n );\n const reported = usage(\n data.usage,\n \"prompt_tokens\",\n \"completion_tokens\",\n \"prompt_tokens_details\",\n );\n return { text: message.content, ...(reported ? { usage: reported } : {}) };\n}\n\nexport function readModelResponse(value: unknown): ModelResult {\n const data = object(value);\n requireResponse(data.status === \"completed\" && data.error == null);\n requireResponse(Array.isArray(data.output));\n const texts: string[] = [];\n for (const value of data.output) {\n const item = object(value);\n if (item.type === \"reasoning\") continue;\n requireResponse(\n item.type === \"message\" &&\n item.role === \"assistant\" &&\n item.status === \"completed\",\n );\n requireResponse(Array.isArray(item.content));\n for (const value of item.content) {\n const part = object(value);\n requireResponse(\n part.type === \"output_text\" && typeof part.text === \"string\",\n );\n texts.push(part.text);\n }\n }\n requireResponse(texts.length > 0);\n const reported = usage(\n data.usage,\n \"input_tokens\",\n \"output_tokens\",\n \"input_tokens_details\",\n );\n return { text: texts.join(\"\"), ...(reported ? { usage: reported } : {}) };\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import type { TransportReference } from "../domain/transport.types.ts";
1
2
  import type { AgentAdapter, AgentObservation, Usage } from "../domain/agent.types.ts";
2
3
  import type { Brief } from "../domain/prompts.types.ts";
3
4
  import type { ResponseSpec } from "../domain/response.types.ts";
@@ -29,6 +30,7 @@ export interface Turn {
29
30
  readonly status: number;
30
31
  readonly conversation?: string;
31
32
  readonly transcript?: string;
33
+ readonly transcriptReference?: TransportReference;
32
34
  readonly usage: Usage;
33
35
  readonly durationMs: number;
34
36
  }