open-managed-agents 0.1.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 (197) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +248 -0
  3. package/bin/oma.mjs +499 -0
  4. package/dist/scripts/alpha-openai-compatible-fixture.mjs +134 -0
  5. package/dist/scripts/alpha-smoke-models.mjs +92 -0
  6. package/dist/scripts/alpha-smoke.mjs +581 -0
  7. package/dist/scripts/oma-doctor.js +230 -0
  8. package/dist/scripts/oma-models.js +299 -0
  9. package/dist/scripts/oma-workspaces.js +133 -0
  10. package/dist/src/control-plane/admin/auth.js +43 -0
  11. package/dist/src/control-plane/admin/routes.js +76 -0
  12. package/dist/src/control-plane/admin/service.js +102 -0
  13. package/dist/src/control-plane/admission.js +90 -0
  14. package/dist/src/control-plane/agents/routes.js +63 -0
  15. package/dist/src/control-plane/agents/service.js +687 -0
  16. package/dist/src/control-plane/agents/store.js +391 -0
  17. package/dist/src/control-plane/agents/types.js +1 -0
  18. package/dist/src/control-plane/api-constants.js +3 -0
  19. package/dist/src/control-plane/app.js +907 -0
  20. package/dist/src/control-plane/console/auth.js +68 -0
  21. package/dist/src/control-plane/console/static.js +112 -0
  22. package/dist/src/control-plane/deployment-runtime-config.js +328 -0
  23. package/dist/src/control-plane/deployment-runtime-event-coordinator.js +25 -0
  24. package/dist/src/control-plane/deployment-runtime-turn-guard.js +14 -0
  25. package/dist/src/control-plane/deployment-session-coordinator.js +25 -0
  26. package/dist/src/control-plane/deployment-session-output-coordinator.js +27 -0
  27. package/dist/src/control-plane/deployment-storage.js +354 -0
  28. package/dist/src/control-plane/egress/image.js +48 -0
  29. package/dist/src/control-plane/egress/policy.js +571 -0
  30. package/dist/src/control-plane/egress/presets.js +77 -0
  31. package/dist/src/control-plane/egress/proxy.js +72 -0
  32. package/dist/src/control-plane/egress/ssrf.js +106 -0
  33. package/dist/src/control-plane/egress/utils/debug.js +14 -0
  34. package/dist/src/control-plane/egress/vendor/http-proxy.js +335 -0
  35. package/dist/src/control-plane/egress/vendor/mitm-ca.js +226 -0
  36. package/dist/src/control-plane/egress/vendor/mitm-leaf.js +124 -0
  37. package/dist/src/control-plane/egress/vendor/parent-proxy.js +438 -0
  38. package/dist/src/control-plane/egress/vendor/request-filter.js +105 -0
  39. package/dist/src/control-plane/egress/vendor/sandbox-config.js +1 -0
  40. package/dist/src/control-plane/egress/vendor/tls-terminate-proxy.js +269 -0
  41. package/dist/src/control-plane/environments/routes.js +54 -0
  42. package/dist/src/control-plane/environments/service.js +112 -0
  43. package/dist/src/control-plane/environments/store.js +132 -0
  44. package/dist/src/control-plane/environments/types.js +1 -0
  45. package/dist/src/control-plane/errors.js +81 -0
  46. package/dist/src/control-plane/events/broadcaster.js +217 -0
  47. package/dist/src/control-plane/events/constants.js +2 -0
  48. package/dist/src/control-plane/events/persist.js +39 -0
  49. package/dist/src/control-plane/events/request.js +190 -0
  50. package/dist/src/control-plane/events/routes.js +159 -0
  51. package/dist/src/control-plane/events/runtime-helpers.js +134 -0
  52. package/dist/src/control-plane/events/service.js +1985 -0
  53. package/dist/src/control-plane/events/session-guards.js +29 -0
  54. package/dist/src/control-plane/events/sse.js +6 -0
  55. package/dist/src/control-plane/events/store.js +772 -0
  56. package/dist/src/control-plane/events/tool-persistence.js +102 -0
  57. package/dist/src/control-plane/events/types.js +21 -0
  58. package/dist/src/control-plane/files/routes.js +121 -0
  59. package/dist/src/control-plane/files/service.js +65 -0
  60. package/dist/src/control-plane/files/store-common.js +87 -0
  61. package/dist/src/control-plane/files/store-local.js +488 -0
  62. package/dist/src/control-plane/files/store-memory.js +270 -0
  63. package/dist/src/control-plane/files/store.js +2 -0
  64. package/dist/src/control-plane/files/types.js +7 -0
  65. package/dist/src/control-plane/http.js +25 -0
  66. package/dist/src/control-plane/ids.js +43 -0
  67. package/dist/src/control-plane/logging.js +263 -0
  68. package/dist/src/control-plane/models/auth-storage-backend.js +238 -0
  69. package/dist/src/control-plane/models/catalog.js +78 -0
  70. package/dist/src/control-plane/models/config-security.js +376 -0
  71. package/dist/src/control-plane/models/deployment-config.js +93 -0
  72. package/dist/src/control-plane/models/routes.js +32 -0
  73. package/dist/src/control-plane/models/service.js +138 -0
  74. package/dist/src/control-plane/observability/instruments.js +65 -0
  75. package/dist/src/control-plane/observability/metrics.js +125 -0
  76. package/dist/src/control-plane/observability/routes.js +95 -0
  77. package/dist/src/control-plane/openapi/document.js +282 -0
  78. package/dist/src/control-plane/openapi/routes.js +78 -0
  79. package/dist/src/control-plane/request-idempotency.js +85 -0
  80. package/dist/src/control-plane/secrets/envelope.js +132 -0
  81. package/dist/src/control-plane/secrets/master-key.js +44 -0
  82. package/dist/src/control-plane/secrets/routes.js +20 -0
  83. package/dist/src/control-plane/secrets/service.js +71 -0
  84. package/dist/src/control-plane/secrets/store.js +212 -0
  85. package/dist/src/control-plane/secrets/types.js +3 -0
  86. package/dist/src/control-plane/sessions/pi/custom-tools.js +146 -0
  87. package/dist/src/control-plane/sessions/pi/mcp/bridge.js +527 -0
  88. package/dist/src/control-plane/sessions/pi/mcp/client.js +157 -0
  89. package/dist/src/control-plane/sessions/pi/mcp/credential.js +28 -0
  90. package/dist/src/control-plane/sessions/pi/mcp/fetch.js +69 -0
  91. package/dist/src/control-plane/sessions/pi/mcp/probe.js +202 -0
  92. package/dist/src/control-plane/sessions/pi/mcp/runtime.js +13 -0
  93. package/dist/src/control-plane/sessions/pi/runner.js +1104 -0
  94. package/dist/src/control-plane/sessions/pi/sandbox/cma-glob.js +388 -0
  95. package/dist/src/control-plane/sessions/pi/sandbox/cma-grep.js +124 -0
  96. package/dist/src/control-plane/sessions/pi/sandbox/docker-egress.js +347 -0
  97. package/dist/src/control-plane/sessions/pi/sandbox/docker.js +1589 -0
  98. package/dist/src/control-plane/sessions/pi/sandbox/glob.js +48 -0
  99. package/dist/src/control-plane/sessions/pi/sandbox/image.js +7 -0
  100. package/dist/src/control-plane/sessions/pi/sandbox/microsandbox.js +1677 -0
  101. package/dist/src/control-plane/sessions/pi/sandbox/provider.js +561 -0
  102. package/dist/src/control-plane/sessions/pi/sandbox/selection.js +168 -0
  103. package/dist/src/control-plane/sessions/pi/span-normalizer.js +82 -0
  104. package/dist/src/control-plane/sessions/pi/tool-permissions.js +392 -0
  105. package/dist/src/control-plane/sessions/pi/translator.js +100 -0
  106. package/dist/src/control-plane/sessions/request.js +164 -0
  107. package/dist/src/control-plane/sessions/resources.js +63 -0
  108. package/dist/src/control-plane/sessions/routes.js +88 -0
  109. package/dist/src/control-plane/sessions/serialize.js +24 -0
  110. package/dist/src/control-plane/sessions/service.js +733 -0
  111. package/dist/src/control-plane/sessions/store.js +538 -0
  112. package/dist/src/control-plane/sessions/types.js +1 -0
  113. package/dist/src/control-plane/skills/archive.js +169 -0
  114. package/dist/src/control-plane/skills/routes.js +41 -0
  115. package/dist/src/control-plane/skills/service.js +29 -0
  116. package/dist/src/control-plane/skills/store.js +233 -0
  117. package/dist/src/control-plane/skills/types.js +5 -0
  118. package/dist/src/control-plane/sqlite-transaction.js +15 -0
  119. package/dist/src/control-plane/vaults/mcp-oauth-validate.js +165 -0
  120. package/dist/src/control-plane/vaults/oauth-refresh-ticker.js +43 -0
  121. package/dist/src/control-plane/vaults/oauth-refresh.js +448 -0
  122. package/dist/src/control-plane/vaults/routes.js +66 -0
  123. package/dist/src/control-plane/vaults/service.js +553 -0
  124. package/dist/src/control-plane/vaults/store.js +691 -0
  125. package/dist/src/control-plane/vaults/types.js +1 -0
  126. package/dist/src/control-plane/wake-loop.js +65 -0
  127. package/dist/src/control-plane/wiring.js +92 -0
  128. package/dist/src/control-plane/workspace.js +4 -0
  129. package/dist/src/control-plane/workspaces/store.js +158 -0
  130. package/dist/src/egress-proxy-main.js +103 -0
  131. package/dist/src/main.js +124 -0
  132. package/dist/src/types/agents.js +1 -0
  133. package/dist/src/types/common.js +1 -0
  134. package/dist/src/types/environments.js +1 -0
  135. package/dist/src/types/events.js +121 -0
  136. package/dist/src/types/files.js +1 -0
  137. package/dist/src/types/json.js +19 -0
  138. package/dist/src/types/sessions.js +1 -0
  139. package/dist/ui/managed-agents-console/README.md +52 -0
  140. package/dist/ui/managed-agents-console/docs/agents.md +23 -0
  141. package/dist/ui/managed-agents-console/docs/console.md +23 -0
  142. package/dist/ui/managed-agents-console/docs/dreams.md +5 -0
  143. package/dist/ui/managed-agents-console/docs/environments.md +27 -0
  144. package/dist/ui/managed-agents-console/docs/events.md +19 -0
  145. package/dist/ui/managed-agents-console/docs/files.md +17 -0
  146. package/dist/ui/managed-agents-console/docs/github.md +11 -0
  147. package/dist/ui/managed-agents-console/docs/integrations.md +19 -0
  148. package/dist/ui/managed-agents-console/docs/memory.md +11 -0
  149. package/dist/ui/managed-agents-console/docs/migration.md +17 -0
  150. package/dist/ui/managed-agents-console/docs/multiagent.md +11 -0
  151. package/dist/ui/managed-agents-console/docs/outcomes.md +11 -0
  152. package/dist/ui/managed-agents-console/docs/overview.md +41 -0
  153. package/dist/ui/managed-agents-console/docs/permissions.md +24 -0
  154. package/dist/ui/managed-agents-console/docs/quickstart.md +51 -0
  155. package/dist/ui/managed-agents-console/docs/reference.md +27 -0
  156. package/dist/ui/managed-agents-console/docs/sandbox-reference.md +19 -0
  157. package/dist/ui/managed-agents-console/docs/sandbox-security.md +15 -0
  158. package/dist/ui/managed-agents-console/docs/scheduled-deployments.md +5 -0
  159. package/dist/ui/managed-agents-console/docs/self-hosted-sandboxes.md +15 -0
  160. package/dist/ui/managed-agents-console/docs/session-operations.md +21 -0
  161. package/dist/ui/managed-agents-console/docs/sessions.md +21 -0
  162. package/dist/ui/managed-agents-console/docs/skills.md +15 -0
  163. package/dist/ui/managed-agents-console/docs/tools.md +23 -0
  164. package/dist/ui/managed-agents-console/docs/vaults.md +19 -0
  165. package/dist/ui/managed-agents-console/docs/webhooks.md +11 -0
  166. package/dist/ui/managed-agents-console/index.html +47 -0
  167. package/dist/ui/managed-agents-console/serve.mjs +116 -0
  168. package/dist/ui/managed-agents-console/src/agents-files.jsx +273 -0
  169. package/dist/ui/managed-agents-console/src/api.js +1043 -0
  170. package/dist/ui/managed-agents-console/src/app.jsx +633 -0
  171. package/dist/ui/managed-agents-console/src/auth.jsx +215 -0
  172. package/dist/ui/managed-agents-console/src/console.css +733 -0
  173. package/dist/ui/managed-agents-console/src/data.js +188 -0
  174. package/dist/ui/managed-agents-console/src/detail.jsx +719 -0
  175. package/dist/ui/managed-agents-console/src/docs.jsx +200 -0
  176. package/dist/ui/managed-agents-console/src/environments.jsx +365 -0
  177. package/dist/ui/managed-agents-console/src/forms.jsx +494 -0
  178. package/dist/ui/managed-agents-console/src/icons.jsx +48 -0
  179. package/dist/ui/managed-agents-console/src/skills.jsx +21 -0
  180. package/dist/ui/managed-agents-console/src/sse.js +185 -0
  181. package/dist/ui/managed-agents-console/src/states.jsx +90 -0
  182. package/dist/ui/managed-agents-console/src/tweaks-panel.jsx +541 -0
  183. package/dist/ui/managed-agents-console/src/ui.jsx +190 -0
  184. package/dist/ui/managed-agents-console/src/vaults-data.js +89 -0
  185. package/dist/ui/managed-agents-console/src/vaults.jsx +171 -0
  186. package/dist/ui/managed-agents-console/vendor/babel.min.js +4 -0
  187. package/dist/ui/managed-agents-console/vendor/react-dom.production.min.js +267 -0
  188. package/dist/ui/managed-agents-console/vendor/react.production.min.js +31 -0
  189. package/dist/ui/openapi-docs/VENDOR.md +27 -0
  190. package/dist/ui/openapi-docs/index.html +16 -0
  191. package/dist/ui/openapi-docs/swagger-initializer.js +14 -0
  192. package/dist/ui/openapi-docs/vendor/LICENSE +202 -0
  193. package/dist/ui/openapi-docs/vendor/NOTICE +2 -0
  194. package/dist/ui/openapi-docs/vendor/swagger-ui-bundle.js +2 -0
  195. package/dist/ui/openapi-docs/vendor/swagger-ui-standalone-preset.js +2 -0
  196. package/dist/ui/openapi-docs/vendor/swagger-ui.css +3 -0
  197. package/package.json +53 -0
@@ -0,0 +1,687 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { newAgentId } from "../ids.js";
3
+ import { conflict, invalidRequest, notFound } from "../errors.js";
4
+ import { isJsonObject, isJsonValue } from "../../types/json.js";
5
+ import { AgentUpdateArchivedError, AgentUpdateConflictError, AgentUpdateMissingError, } from "./store.js";
6
+ const MAX_SKILLS = 20;
7
+ const CMA_BUILTIN_TOOL_NAMES = [
8
+ "bash",
9
+ "edit",
10
+ "glob",
11
+ "grep",
12
+ "read",
13
+ "web_fetch",
14
+ "web_search",
15
+ "write",
16
+ ];
17
+ const CMA_PERMISSION_POLICY_TYPES = ["always_allow", "always_ask"];
18
+ const OMA_UNSUPPORTED_BUILTIN_TOOL_NAMES = [
19
+ "web_fetch",
20
+ "web_search",
21
+ ];
22
+ const MULTIAGENT_UNSUPPORTED_MESSAGE = "The `multiagent` configuration is not supported by this deployment.";
23
+ const ACCEPT_ANY_MODEL = { assertAvailable: () => { } };
24
+ export class DefaultAgentService {
25
+ store;
26
+ skills;
27
+ models;
28
+ constructor(store, skills, models = ACCEPT_ANY_MODEL) {
29
+ this.store = store;
30
+ this.skills = skills;
31
+ this.models = models;
32
+ }
33
+ create(workspaceId, input) {
34
+ const req = parseCreateAgent(input);
35
+ const model = normalizeModel(req.model);
36
+ this.models.assertAvailable(model);
37
+ this.assertSkillAttachments(workspaceId, req.skills ?? []);
38
+ const now = new Date().toISOString();
39
+ const id = newAgentId();
40
+ const row = {
41
+ id,
42
+ workspace_id: workspaceId,
43
+ type: "agent",
44
+ name: req.name,
45
+ model,
46
+ system: req.system ?? null,
47
+ description: req.description ?? null,
48
+ tools: req.tools ?? [],
49
+ skills: req.skills ?? [],
50
+ mcp_servers: req.mcp_servers ?? [],
51
+ metadata: req.metadata ?? {},
52
+ multiagent: req.multiagent ?? null,
53
+ version: 1,
54
+ created_at: now,
55
+ updated_at: now,
56
+ archived_at: null,
57
+ };
58
+ return toManagedAgent(this.store.create({ row }));
59
+ }
60
+ update(workspaceId, agentId, input) {
61
+ const current = this.store.retrieveAny(workspaceId, agentId);
62
+ if (!current)
63
+ throw notFound(`Agent ${agentId} not found`);
64
+ if (current.archived_at !== null) {
65
+ throw invalidRequest("Cannot modify archived agent");
66
+ }
67
+ const patch = objectInput(input);
68
+ const expectedVersion = positiveIntegerField(patch, "version");
69
+ if (expectedVersion !== current.version) {
70
+ throw conflict("Concurrent modification detected. Please fetch the latest version and retry.");
71
+ }
72
+ const mergedMetadata = patchMetadata(current.metadata, patch.metadata);
73
+ const mergedInput = {
74
+ name: patch.name === undefined ? current.name : patch.name,
75
+ model: patch.model === undefined ? current.model : patch.model,
76
+ system: patch.system === undefined ? current.system : patch.system,
77
+ description: patch.description === undefined
78
+ ? current.description
79
+ : patch.description,
80
+ tools: patch.tools === undefined ? current.tools : (patch.tools ?? []),
81
+ skills: patch.skills === undefined ? current.skills : (patch.skills ?? []),
82
+ mcp_servers: patch.mcp_servers === undefined
83
+ ? current.mcp_servers
84
+ : (patch.mcp_servers ?? []),
85
+ metadata: mergedMetadata,
86
+ multiagent: patch.multiagent === undefined
87
+ ? current.multiagent
88
+ : patch.multiagent,
89
+ };
90
+ const req = parseCreateAgent(mergedInput, {
91
+ allowUnsupportedMultiagent: patch.multiagent === undefined && current.multiagent !== null,
92
+ });
93
+ const model = normalizeModel(req.model);
94
+ this.models.assertAvailable(model);
95
+ this.assertSkillAttachments(workspaceId, req.skills ?? []);
96
+ const candidate = {
97
+ ...current,
98
+ name: req.name,
99
+ model,
100
+ system: req.system ?? null,
101
+ description: req.description ?? null,
102
+ tools: req.tools ?? [],
103
+ skills: req.skills ?? [],
104
+ mcp_servers: req.mcp_servers ?? [],
105
+ metadata: req.metadata ?? {},
106
+ multiagent: req.multiagent ?? null,
107
+ };
108
+ if (sameAgentConfiguration(current, candidate))
109
+ return toManagedAgent(current);
110
+ const now = new Date().toISOString();
111
+ const next = {
112
+ ...candidate,
113
+ version: current.version + 1,
114
+ updated_at: now,
115
+ };
116
+ try {
117
+ return toManagedAgent(this.store.update({ expectedVersion, row: next }));
118
+ }
119
+ catch (error) {
120
+ if (error instanceof AgentUpdateMissingError) {
121
+ throw notFound(`Agent ${agentId} not found`);
122
+ }
123
+ if (error instanceof AgentUpdateArchivedError) {
124
+ throw invalidRequest("Cannot modify archived agent");
125
+ }
126
+ if (error instanceof AgentUpdateConflictError) {
127
+ throw conflict("Concurrent modification detected. Please fetch the latest version and retry.");
128
+ }
129
+ throw error;
130
+ }
131
+ }
132
+ assertSkillAttachments(workspaceId, attachments) {
133
+ for (const attachment of attachments) {
134
+ if (attachment.type === "anthropic") {
135
+ throw invalidRequest("anthropic prebuilt skills are not available on this deployment");
136
+ }
137
+ if (!this.skills?.getSkill(workspaceId, attachment.skill_id)) {
138
+ throw invalidRequest(`Unknown custom skill_id: ${attachment.skill_id}`);
139
+ }
140
+ const version = attachment.version ?? "latest";
141
+ if (!this.skills.getVersion(workspaceId, attachment.skill_id, version)) {
142
+ throw invalidRequest(`Agent has invalid configuration: \`skill_id\` \`${attachment.skill_id}\` version \`${version}\` not found`);
143
+ }
144
+ }
145
+ }
146
+ retrieve(workspaceId, agentId, version) {
147
+ const owner = this.store.retrieveAny(workspaceId, agentId);
148
+ if (!owner)
149
+ throw notFound(`Agent ${agentId} not found`);
150
+ if (version === undefined)
151
+ return toManagedAgent(owner);
152
+ const row = this.store.retrieveVersion(workspaceId, agentId, version);
153
+ if (!row)
154
+ throw notFound("Agent version not found.");
155
+ return toManagedAgent(row);
156
+ }
157
+ listVersions(workspaceId, agentId, opts = {}) {
158
+ try {
159
+ const page = this.store.listVersions(workspaceId, agentId, opts);
160
+ return { data: page.data.map(toManagedAgent), next_page: page.next_page };
161
+ }
162
+ catch (error) {
163
+ if (error instanceof AgentUpdateMissingError) {
164
+ throw notFound(`Agent ${agentId} not found`);
165
+ }
166
+ if (error instanceof Error && error.message === "invalid page cursor") {
167
+ throw invalidRequest("invalid page cursor");
168
+ }
169
+ throw error;
170
+ }
171
+ }
172
+ archive(workspaceId, agentId) {
173
+ const archivedAt = new Date().toISOString();
174
+ const row = this.store.archive(workspaceId, agentId, archivedAt);
175
+ if (!row) {
176
+ throw notFound(`Agent ${agentId} not found`);
177
+ }
178
+ return toManagedAgent(row);
179
+ }
180
+ list(workspaceId, opts = {}) {
181
+ const page = this.store.list(workspaceId, opts);
182
+ return {
183
+ data: page.data.map(toManagedAgent),
184
+ has_more: page.has_more,
185
+ next_page: page.next_page,
186
+ };
187
+ }
188
+ }
189
+ function toManagedAgent(row) {
190
+ return {
191
+ id: row.id,
192
+ type: row.type,
193
+ name: row.name,
194
+ model: row.model,
195
+ system: row.system,
196
+ description: row.description,
197
+ tools: row.tools,
198
+ skills: row.skills,
199
+ mcp_servers: row.mcp_servers,
200
+ metadata: row.metadata,
201
+ multiagent: row.multiagent,
202
+ version: row.version,
203
+ created_at: row.created_at,
204
+ updated_at: row.updated_at,
205
+ archived_at: row.archived_at,
206
+ };
207
+ }
208
+ function parseCreateAgent(input, opts = {}) {
209
+ const obj = objectInput(input);
210
+ if (obj.multiagent !== undefined &&
211
+ obj.multiagent !== null &&
212
+ opts.allowUnsupportedMultiagent !== true) {
213
+ // Do this before parsing the other agent fields: a non-null config must
214
+ // never be accepted as a durable promise for a runtime we do not have.
215
+ throw invalidRequest(MULTIAGENT_UNSUPPORTED_MESSAGE);
216
+ }
217
+ const name = stringField(obj, "name", { required: true });
218
+ const system = nullableStringField(obj, "system");
219
+ const description = nullableStringField(obj, "description");
220
+ const model = modelField(obj);
221
+ const tools = toolArrayField(obj, "tools");
222
+ const skills = skillArrayField(obj, "skills");
223
+ const mcpServers = mcpServerArrayField(obj, "mcp_servers");
224
+ assertMcpServerToolsetCrossReferences(mcpServers, tools);
225
+ const metadata = metadataField(obj);
226
+ const multiagent = multiagentField(obj);
227
+ return {
228
+ name,
229
+ model,
230
+ system,
231
+ description,
232
+ tools,
233
+ skills,
234
+ mcp_servers: mcpServers,
235
+ metadata,
236
+ multiagent,
237
+ };
238
+ }
239
+ function objectInput(input) {
240
+ if (!isJsonObject(input)) {
241
+ throw invalidRequest("Request body must be a JSON object");
242
+ }
243
+ return input;
244
+ }
245
+ function stringField(obj, field, opts = {}) {
246
+ const value = obj[field];
247
+ if (typeof value === "string" && value.length > 0)
248
+ return value;
249
+ if (value === undefined && opts.required !== true)
250
+ return "";
251
+ throw invalidRequest(`\`${field}\` must be a non-empty string`);
252
+ }
253
+ function nullableStringField(obj, field) {
254
+ const value = obj[field];
255
+ if (value === undefined)
256
+ return undefined;
257
+ if (value === null)
258
+ return null;
259
+ if (typeof value === "string")
260
+ return value;
261
+ throw invalidRequest(`\`${field}\` must be a string or null`);
262
+ }
263
+ function modelField(obj) {
264
+ const value = obj.model;
265
+ if (typeof value === "string" && value.length > 0)
266
+ return value;
267
+ if (isJsonObject(value)) {
268
+ const modelObj = value;
269
+ for (const key of Object.keys(modelObj)) {
270
+ if (key !== "provider" && key !== "id" && key !== "speed") {
271
+ throw invalidRequest(`Unknown field \`model.${key}\``);
272
+ }
273
+ }
274
+ const provider = modelObj.provider;
275
+ if (provider !== undefined &&
276
+ (typeof provider !== "string" || provider.length === 0)) {
277
+ throw invalidRequest("`model.provider` must be a non-empty string");
278
+ }
279
+ const id = stringField(modelObj, "id", { required: true });
280
+ const speed = modelObj.speed;
281
+ if (speed !== undefined &&
282
+ speed !== "standard" &&
283
+ speed !== "fast") {
284
+ throw invalidRequest("`model.speed` must be `standard` or `fast`");
285
+ }
286
+ return {
287
+ ...(provider === undefined ? {} : { provider }),
288
+ id,
289
+ ...(speed === undefined ? {} : { speed }),
290
+ };
291
+ }
292
+ throw invalidRequest("`model` must be a non-empty string or model object");
293
+ }
294
+ function toolArrayField(obj, field) {
295
+ const value = obj[field];
296
+ if (value === undefined)
297
+ return undefined;
298
+ if (!Array.isArray(value))
299
+ throw invalidRequest(`\`${field}\` must be an array`);
300
+ const tools = value.map(parseTool);
301
+ const agentToolsetCount = tools.filter((tool) => tool.type === "agent_toolset_20260401").length;
302
+ if (agentToolsetCount > 1) {
303
+ throw invalidRequest("`tools` may contain at most one `agent_toolset_20260401` entry");
304
+ }
305
+ return tools;
306
+ }
307
+ function parseTool(value) {
308
+ const tool = jsonObjectField(value, "tools");
309
+ const type = stringField(tool, "type", { required: true });
310
+ if (type === "agent_toolset_20260401") {
311
+ // Probe 63 shows a narrow precedence distinction: a structurally
312
+ // malformed default policy yields to config validation, while a
313
+ // semantically unknown default policy wins before configs are inspected.
314
+ const parseConfigsFirst = hasStructurallyMalformedDefaultPolicy(tool);
315
+ const defaultConfig = parseConfigsFirst
316
+ ? undefined
317
+ : optionalDefaultConfigSpread(tool, { materialize: true });
318
+ const configsSpread = optionalToolConfigsSpread(tool, {
319
+ builtin: true,
320
+ materialize: true,
321
+ });
322
+ const defaultConfigSpread = parseConfigsFirst
323
+ ? optionalDefaultConfigSpread(tool, { materialize: true })
324
+ : defaultConfig;
325
+ const explicitConfigs = configsSpread.configs ?? [];
326
+ const builtinToolset = {
327
+ type,
328
+ ...defaultConfigSpread,
329
+ configs: materializeDeploymentBuiltinDefaults(explicitConfigs),
330
+ };
331
+ assertUnsupportedBuiltinToolsDisabled({
332
+ default_config: builtinToolset.default_config,
333
+ configs: explicitConfigs,
334
+ });
335
+ return builtinToolset;
336
+ }
337
+ if (type === "mcp_toolset") {
338
+ return {
339
+ type,
340
+ mcp_server_name: stringField(tool, "mcp_server_name", {
341
+ required: true,
342
+ }),
343
+ ...optionalDefaultConfigSpread(tool),
344
+ ...optionalToolConfigsSpread(tool),
345
+ };
346
+ }
347
+ if (type === "custom") {
348
+ const inputSchema = jsonObjectRequired(tool, "input_schema");
349
+ const description = optionalStringField(tool, "description");
350
+ return description === undefined
351
+ ? {
352
+ type,
353
+ name: stringField(tool, "name", { required: true }),
354
+ input_schema: inputSchema,
355
+ }
356
+ : {
357
+ type,
358
+ name: stringField(tool, "name", { required: true }),
359
+ description,
360
+ input_schema: inputSchema,
361
+ };
362
+ }
363
+ throw invalidRequest("`tools[].type` must be `agent_toolset_20260401`, `mcp_toolset`, or `custom`");
364
+ }
365
+ function skillArrayField(obj, field) {
366
+ const value = obj[field];
367
+ if (value === undefined)
368
+ return undefined;
369
+ if (!Array.isArray(value))
370
+ throw invalidRequest(`\`${field}\` must be an array`);
371
+ if (value.length > MAX_SKILLS) {
372
+ throw invalidRequest(`\`${field}\` may contain at most ${MAX_SKILLS} skills`);
373
+ }
374
+ const seen = new Set();
375
+ return value.map((v) => {
376
+ const skill = jsonObjectField(v, field);
377
+ const type = stringField(skill, "type", { required: true });
378
+ if (type !== "custom" && type !== "anthropic") {
379
+ throw invalidRequest("`skills[].type` must be `custom` or `anthropic`");
380
+ }
381
+ const skillId = stringField(skill, "skill_id", { required: true });
382
+ if (seen.has(skillId)) {
383
+ throw invalidRequest(`Agent has invalid configuration: duplicate skill_id "${skillId}"`);
384
+ }
385
+ seen.add(skillId);
386
+ const version = optionalStringField(skill, "version");
387
+ return version === undefined
388
+ ? { type, skill_id: skillId }
389
+ : { type, skill_id: skillId, version };
390
+ });
391
+ }
392
+ const MAX_MCP_SERVERS = 20;
393
+ const MAX_MCP_SERVER_NAME_LENGTH = 255;
394
+ const MAX_MCP_SERVER_URL_LENGTH = 2048;
395
+ function mcpServerArrayField(obj, field) {
396
+ const value = obj[field];
397
+ if (value === undefined)
398
+ return undefined;
399
+ if (!Array.isArray(value))
400
+ throw invalidRequest(`\`${field}\` must be an array`);
401
+ if (value.length > MAX_MCP_SERVERS) {
402
+ throw invalidRequest(`\`${field}\` may contain at most ${MAX_MCP_SERVERS} servers`);
403
+ }
404
+ const seenNames = new Set();
405
+ return value.map((v) => {
406
+ const server = jsonObjectField(v, field);
407
+ rejectUnknownMcpServerFields(server, field);
408
+ const type = stringField(server, "type", { required: true });
409
+ if (type !== "url") {
410
+ throw invalidRequest("Only url MCP server definitions are supported in MVP");
411
+ }
412
+ const name = stringField(server, "name", { required: true });
413
+ if (name.length > MAX_MCP_SERVER_NAME_LENGTH) {
414
+ throw invalidRequest(`\`${field}[].name\` must be at most ${MAX_MCP_SERVER_NAME_LENGTH} characters`);
415
+ }
416
+ // Name comparisons are case-sensitive throughout (uniqueness here,
417
+ // toolset cross-references below, config tool matching at runtime).
418
+ if (seenNames.has(name)) {
419
+ throw invalidRequest(`\`${field}\` contains duplicate server name: ${name}`);
420
+ }
421
+ seenNames.add(name);
422
+ // `url` is validated via `new URL` but the RAW input string is what gets
423
+ // stored: URL normalization (port stripping, host lowercasing, trailing
424
+ // slash) would silently break M2's byte-exact credential matching.
425
+ const url = stringField(server, "url", { required: true });
426
+ if (url.length > MAX_MCP_SERVER_URL_LENGTH) {
427
+ throw invalidRequest(`\`${field}[].url\` must be at most ${MAX_MCP_SERVER_URL_LENGTH} characters`);
428
+ }
429
+ let parsed;
430
+ try {
431
+ parsed = new URL(url);
432
+ }
433
+ catch {
434
+ throw invalidRequest(`\`${field}[].url\` must be a valid URL: ${url}`);
435
+ }
436
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
437
+ throw invalidRequest(`\`${field}[].url\` must use http or https: ${url}`);
438
+ }
439
+ if (parsed.username !== "" || parsed.password !== "") {
440
+ throw invalidRequest(`\`${field}[].url\` must not embed credentials; use vault credentials instead`);
441
+ }
442
+ return { type, name, url };
443
+ });
444
+ }
445
+ function rejectUnknownMcpServerFields(server, field) {
446
+ for (const key of Object.keys(server)) {
447
+ if (key === "type" || key === "name" || key === "url")
448
+ continue;
449
+ throw invalidRequest(`Unsupported \`${field}[]\` field: ${key}`);
450
+ }
451
+ }
452
+ // Upstream rejects agent definitions with unreferenced servers, dangling
453
+ // toolsets (both-ways referencing), and duplicate toolsets per server — all
454
+ // confirmed against hosted by live probe 47 (scratch/47-mcp-hosted-probe.md).
455
+ // The userinfo-URL rejection below is a deliberate OMA deviation (hosted
456
+ // accepts embedded credentials; we refuse the leak class).
457
+ function assertMcpServerToolsetCrossReferences(servers, tools) {
458
+ const serverNames = new Set((servers ?? []).map((server) => server.name));
459
+ const referenced = new Set();
460
+ for (const tool of tools ?? []) {
461
+ if (tool.type !== "mcp_toolset")
462
+ continue;
463
+ if (!serverNames.has(tool.mcp_server_name)) {
464
+ throw invalidRequest(`\`tools[]\` mcp_toolset references undeclared MCP server: ${tool.mcp_server_name}`);
465
+ }
466
+ if (referenced.has(tool.mcp_server_name)) {
467
+ throw invalidRequest(`\`tools\` may contain at most one mcp_toolset per server: ${tool.mcp_server_name}`);
468
+ }
469
+ referenced.add(tool.mcp_server_name);
470
+ }
471
+ // Covers both a `tools` array without a matching toolset and a request
472
+ // with `tools` absent entirely — an unreferenced server either way.
473
+ for (const name of serverNames) {
474
+ if (referenced.has(name))
475
+ continue;
476
+ throw invalidRequest(`\`mcp_servers\` entry is not referenced by any mcp_toolset: ${name}`);
477
+ }
478
+ }
479
+ function metadataField(obj) {
480
+ const value = obj.metadata;
481
+ if (value === undefined)
482
+ return undefined;
483
+ if (!isJsonObject(value)) {
484
+ throw invalidRequest("`metadata` must be an object");
485
+ }
486
+ const metadata = {};
487
+ for (const [k, v] of Object.entries(value)) {
488
+ if (typeof v !== "string") {
489
+ throw invalidRequest("`metadata` values must be strings");
490
+ }
491
+ metadata[k] = v;
492
+ }
493
+ return metadata;
494
+ }
495
+ function multiagentField(obj) {
496
+ // Non-null values are rejected at the start of parseCreateAgent. Retain the
497
+ // null/absent distinction for the public response and legacy row shape.
498
+ return obj.multiagent === null ? null : undefined;
499
+ }
500
+ function optionalStringField(obj, field) {
501
+ const value = obj[field];
502
+ if (value === undefined)
503
+ return undefined;
504
+ if (typeof value === "string")
505
+ return value;
506
+ throw invalidRequest(`\`${field}\` must be a string`);
507
+ }
508
+ function jsonObjectField(value, field) {
509
+ if (!isJsonObject(value)) {
510
+ throw invalidRequest(`\`${field}\` entries must be JSON objects`);
511
+ }
512
+ if (!isJsonValue(value)) {
513
+ throw invalidRequest(`\`${field}\` entries must be JSON-compatible`);
514
+ }
515
+ return value;
516
+ }
517
+ function jsonObjectRequired(obj, field) {
518
+ const value = obj[field];
519
+ if (value === undefined) {
520
+ throw invalidRequest(`\`${field}\` is required`);
521
+ }
522
+ return jsonObjectField(value, field);
523
+ }
524
+ function optionalDefaultConfigSpread(obj, opts = {}) {
525
+ const value = obj.default_config;
526
+ if (value === undefined) {
527
+ return opts.materialize
528
+ ? {
529
+ default_config: {
530
+ enabled: true,
531
+ permission_policy: { type: "always_allow" },
532
+ },
533
+ }
534
+ : {};
535
+ }
536
+ const config = jsonObjectField(value, "default_config");
537
+ const enabled = config.enabled;
538
+ if (enabled !== undefined && typeof enabled !== "boolean") {
539
+ throw invalidRequest("`default_config.enabled` must be a boolean");
540
+ }
541
+ const permissionPolicy = config.permission_policy === undefined
542
+ ? undefined
543
+ : parsePermissionPolicy(config.permission_policy);
544
+ return {
545
+ default_config: {
546
+ ...(enabled === undefined ? {} : { enabled }),
547
+ ...(permissionPolicy === undefined
548
+ ? {}
549
+ : { permission_policy: permissionPolicy }),
550
+ },
551
+ };
552
+ }
553
+ function hasStructurallyMalformedDefaultPolicy(obj) {
554
+ const defaultConfig = obj.default_config;
555
+ if (!isJsonObject(defaultConfig))
556
+ return defaultConfig !== undefined;
557
+ const policy = defaultConfig.permission_policy;
558
+ if (policy === undefined)
559
+ return false;
560
+ if (typeof policy === "string")
561
+ return policy.length === 0;
562
+ if (!isJsonObject(policy))
563
+ return true;
564
+ return typeof policy.type !== "string" || policy.type.length === 0;
565
+ }
566
+ function optionalToolConfigsSpread(obj, opts = {}) {
567
+ const value = obj.configs;
568
+ if (value === undefined)
569
+ return opts.materialize ? { configs: [] } : {};
570
+ if (!Array.isArray(value)) {
571
+ throw invalidRequest("`configs` must be an array");
572
+ }
573
+ const configs = value.map((v) => {
574
+ const config = jsonObjectField(v, "configs");
575
+ const enabled = config.enabled;
576
+ if (enabled !== undefined && typeof enabled !== "boolean") {
577
+ throw invalidRequest("`configs[].enabled` must be a boolean");
578
+ }
579
+ // Parse policy before the name so an unknown policy wins over an unknown
580
+ // name in the same config, matching the observed hosted precedence.
581
+ const permissionPolicy = config.permission_policy === undefined
582
+ ? undefined
583
+ : parsePermissionPolicy(config.permission_policy);
584
+ const name = stringField(config, "name", { required: true });
585
+ if (opts.builtin && !isCmaBuiltinToolName(name)) {
586
+ throw invalidRequest(`\`configs[].name\` "${name}" is not a valid value; expected one of ${CMA_BUILTIN_TOOL_NAMES.join(", ")}`);
587
+ }
588
+ return {
589
+ name,
590
+ ...(enabled === undefined ? {} : { enabled }),
591
+ ...(permissionPolicy === undefined
592
+ ? {}
593
+ : { permission_policy: permissionPolicy }),
594
+ };
595
+ });
596
+ if (opts.builtin) {
597
+ const seen = new Set();
598
+ for (const config of configs) {
599
+ if (seen.has(config.name)) {
600
+ throw invalidRequest(`\`configs\` contains duplicate builtin tool config: ${config.name}`);
601
+ }
602
+ seen.add(config.name);
603
+ }
604
+ }
605
+ return { configs };
606
+ }
607
+ function isCmaBuiltinToolName(value) {
608
+ return CMA_BUILTIN_TOOL_NAMES.includes(value);
609
+ }
610
+ function assertUnsupportedBuiltinToolsDisabled(toolset) {
611
+ const enabledByDefault = toolset.default_config.enabled ?? true;
612
+ for (const name of OMA_UNSUPPORTED_BUILTIN_TOOL_NAMES) {
613
+ const override = toolset.configs.find((config) => config.name === name);
614
+ // Omitted unsupported tools use deployment defaults (disabled). An explicit
615
+ // config opts into CMA inheritance and therefore follows default_config.
616
+ const effectivelyEnabled = override === undefined
617
+ ? false
618
+ : override.enabled ?? enabledByDefault;
619
+ if (effectivelyEnabled) {
620
+ throw invalidRequest(`Builtin tool \`${name}\` is not supported by this deployment yet; explicitly disable it to use this toolset`);
621
+ }
622
+ }
623
+ }
624
+ function materializeDeploymentBuiltinDefaults(configs) {
625
+ const names = new Set(configs.map((config) => config.name));
626
+ return [
627
+ ...configs,
628
+ ...OMA_UNSUPPORTED_BUILTIN_TOOL_NAMES
629
+ .filter((name) => !names.has(name))
630
+ .map((name) => ({ name, enabled: false })),
631
+ ];
632
+ }
633
+ function parsePermissionPolicy(value) {
634
+ const type = typeof value === "string" && value.length > 0
635
+ ? value
636
+ : stringField(jsonObjectField(value, "permission_policy"), "type", {
637
+ required: true,
638
+ });
639
+ if (!CMA_PERMISSION_POLICY_TYPES.includes(type)) {
640
+ throw invalidRequest(`\`permission_policy.type\` "${type}" is not a valid value; expected one of ${CMA_PERMISSION_POLICY_TYPES.join(", ")}`);
641
+ }
642
+ return { type: type };
643
+ }
644
+ function positiveIntegerField(obj, field) {
645
+ const value = obj[field];
646
+ if (!Number.isSafeInteger(value) || value <= 0) {
647
+ throw invalidRequest(`\`${field}\` must be a positive integer`);
648
+ }
649
+ return value;
650
+ }
651
+ function patchMetadata(current, patch) {
652
+ if (patch === undefined)
653
+ return { ...current };
654
+ if (!isJsonObject(patch))
655
+ throw invalidRequest("`metadata` must be an object");
656
+ const next = { ...current };
657
+ for (const [key, value] of Object.entries(patch)) {
658
+ if (value === null)
659
+ delete next[key];
660
+ else if (typeof value === "string")
661
+ next[key] = value;
662
+ else
663
+ throw invalidRequest("`metadata` values must be strings or null");
664
+ }
665
+ return next;
666
+ }
667
+ function sameAgentConfiguration(a, b) {
668
+ return isDeepStrictEqual({
669
+ name: a.name, model: a.model, system: a.system, description: a.description,
670
+ tools: a.tools, skills: a.skills, mcp_servers: a.mcp_servers,
671
+ metadata: a.metadata, multiagent: a.multiagent,
672
+ }, {
673
+ name: b.name, model: b.model, system: b.system, description: b.description,
674
+ tools: b.tools, skills: b.skills, mcp_servers: b.mcp_servers,
675
+ metadata: b.metadata, multiagent: b.multiagent,
676
+ });
677
+ }
678
+ function normalizeModel(model) {
679
+ if (typeof model === "string") {
680
+ return { provider: "anthropic", id: model, speed: "standard" };
681
+ }
682
+ return {
683
+ provider: model.provider ?? "anthropic",
684
+ id: model.id,
685
+ speed: model.speed ?? "standard",
686
+ };
687
+ }