@shipfox/api-agent 2.0.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 (274) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +105 -0
  5. package/LICENSE +21 -0
  6. package/dist/config.d.ts +13 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +60 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core/credential-fingerprints.d.ts +10 -0
  11. package/dist/core/credential-fingerprints.d.ts.map +1 -0
  12. package/dist/core/credential-fingerprints.js +65 -0
  13. package/dist/core/credential-fingerprints.js.map +1 -0
  14. package/dist/core/custom-model-provider-config-service.d.ts +26 -0
  15. package/dist/core/custom-model-provider-config-service.d.ts.map +1 -0
  16. package/dist/core/custom-model-provider-config-service.js +390 -0
  17. package/dist/core/custom-model-provider-config-service.js.map +1 -0
  18. package/dist/core/custom-provider-url.d.ts +4 -0
  19. package/dist/core/custom-provider-url.d.ts.map +1 -0
  20. package/dist/core/custom-provider-url.js +23 -0
  21. package/dist/core/custom-provider-url.js.map +1 -0
  22. package/dist/core/discover-custom-model-provider-models.d.ts +3 -0
  23. package/dist/core/discover-custom-model-provider-models.d.ts.map +1 -0
  24. package/dist/core/discover-custom-model-provider-models.js +122 -0
  25. package/dist/core/discover-custom-model-provider-models.js.map +1 -0
  26. package/dist/core/entities/agent-workspace-settings.d.ts +9 -0
  27. package/dist/core/entities/agent-workspace-settings.d.ts.map +1 -0
  28. package/dist/core/entities/agent-workspace-settings.js +3 -0
  29. package/dist/core/entities/agent-workspace-settings.js.map +1 -0
  30. package/dist/core/entities/model-provider-config.d.ts +19 -0
  31. package/dist/core/entities/model-provider-config.d.ts.map +1 -0
  32. package/dist/core/entities/model-provider-config.js +3 -0
  33. package/dist/core/entities/model-provider-config.js.map +1 -0
  34. package/dist/core/errors.d.ts +62 -0
  35. package/dist/core/errors.d.ts.map +1 -0
  36. package/dist/core/errors.js +80 -0
  37. package/dist/core/errors.js.map +1 -0
  38. package/dist/core/harness/claude.d.ts +6 -0
  39. package/dist/core/harness/claude.d.ts.map +1 -0
  40. package/dist/core/harness/claude.js +104 -0
  41. package/dist/core/harness/claude.js.map +1 -0
  42. package/dist/core/harness/index.d.ts +4 -0
  43. package/dist/core/harness/index.d.ts.map +1 -0
  44. package/dist/core/harness/index.js +5 -0
  45. package/dist/core/harness/index.js.map +1 -0
  46. package/dist/core/harness/pi.d.ts +6 -0
  47. package/dist/core/harness/pi.d.ts.map +1 -0
  48. package/dist/core/harness/pi.js +16 -0
  49. package/dist/core/harness/pi.js.map +1 -0
  50. package/dist/core/harness/registry.d.ts +22 -0
  51. package/dist/core/harness/registry.d.ts.map +1 -0
  52. package/dist/core/harness/registry.js +46 -0
  53. package/dist/core/harness/registry.js.map +1 -0
  54. package/dist/core/index.d.ts +12 -0
  55. package/dist/core/index.d.ts.map +1 -0
  56. package/dist/core/index.js +11 -0
  57. package/dist/core/index.js.map +1 -0
  58. package/dist/core/model-provider-catalog.d.ts +3 -0
  59. package/dist/core/model-provider-catalog.d.ts.map +1 -0
  60. package/dist/core/model-provider-catalog.js +25 -0
  61. package/dist/core/model-provider-catalog.js.map +1 -0
  62. package/dist/core/model-provider-config-service.d.ts +32 -0
  63. package/dist/core/model-provider-config-service.d.ts.map +1 -0
  64. package/dist/core/model-provider-config-service.js +133 -0
  65. package/dist/core/model-provider-config-service.js.map +1 -0
  66. package/dist/core/model-provider-validation.d.ts +31 -0
  67. package/dist/core/model-provider-validation.d.ts.map +1 -0
  68. package/dist/core/model-provider-validation.js +220 -0
  69. package/dist/core/model-provider-validation.js.map +1 -0
  70. package/dist/core/resolve-agent-config.d.ts +32 -0
  71. package/dist/core/resolve-agent-config.d.ts.map +1 -0
  72. package/dist/core/resolve-agent-config.js +143 -0
  73. package/dist/core/resolve-agent-config.js.map +1 -0
  74. package/dist/core/resolve-runtime-credentials.d.ts +21 -0
  75. package/dist/core/resolve-runtime-credentials.d.ts.map +1 -0
  76. package/dist/core/resolve-runtime-credentials.js +101 -0
  77. package/dist/core/resolve-runtime-credentials.js.map +1 -0
  78. package/dist/core/workspace-agent-defaults-resolver.d.ts +3 -0
  79. package/dist/core/workspace-agent-defaults-resolver.d.ts.map +1 -0
  80. package/dist/core/workspace-agent-defaults-resolver.js +26 -0
  81. package/dist/core/workspace-agent-defaults-resolver.js.map +1 -0
  82. package/dist/db/agent-defaults-snapshot.d.ts +9 -0
  83. package/dist/db/agent-defaults-snapshot.d.ts.map +1 -0
  84. package/dist/db/agent-defaults-snapshot.js +24 -0
  85. package/dist/db/agent-defaults-snapshot.js.map +1 -0
  86. package/dist/db/agent-workspace-settings.d.ts +12 -0
  87. package/dist/db/agent-workspace-settings.d.ts.map +1 -0
  88. package/dist/db/agent-workspace-settings.js +53 -0
  89. package/dist/db/agent-workspace-settings.js.map +1 -0
  90. package/dist/db/db.d.ts +771 -0
  91. package/dist/db/db.d.ts.map +1 -0
  92. package/dist/db/db.js +20 -0
  93. package/dist/db/db.js.map +1 -0
  94. package/dist/db/index.d.ts +9 -0
  95. package/dist/db/index.d.ts.map +1 -0
  96. package/dist/db/index.js +11 -0
  97. package/dist/db/index.js.map +1 -0
  98. package/dist/db/model-provider-configs.d.ts +38 -0
  99. package/dist/db/model-provider-configs.d.ts.map +1 -0
  100. package/dist/db/model-provider-configs.js +159 -0
  101. package/dist/db/model-provider-configs.js.map +1 -0
  102. package/dist/db/schema/agent-workspace-settings.d.ts +97 -0
  103. package/dist/db/schema/agent-workspace-settings.d.ts.map +1 -0
  104. package/dist/db/schema/agent-workspace-settings.js +26 -0
  105. package/dist/db/schema/agent-workspace-settings.js.map +1 -0
  106. package/dist/db/schema/common.d.ts +2 -0
  107. package/dist/db/schema/common.d.ts.map +1 -0
  108. package/dist/db/schema/common.js +4 -0
  109. package/dist/db/schema/common.js.map +1 -0
  110. package/dist/db/schema/model-provider-configs.d.ts +296 -0
  111. package/dist/db/schema/model-provider-configs.d.ts.map +1 -0
  112. package/dist/db/schema/model-provider-configs.js +52 -0
  113. package/dist/db/schema/model-provider-configs.js.map +1 -0
  114. package/dist/index.d.ts +5 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +18 -0
  117. package/dist/index.js.map +1 -0
  118. package/dist/metrics/index.d.ts +2 -0
  119. package/dist/metrics/index.d.ts.map +1 -0
  120. package/dist/metrics/index.js +3 -0
  121. package/dist/metrics/index.js.map +1 -0
  122. package/dist/metrics/instance.d.ts +10 -0
  123. package/dist/metrics/instance.d.ts.map +1 -0
  124. package/dist/metrics/instance.js +10 -0
  125. package/dist/metrics/instance.js.map +1 -0
  126. package/dist/presentation/dto/index.d.ts +2 -0
  127. package/dist/presentation/dto/index.d.ts.map +1 -0
  128. package/dist/presentation/dto/index.js +3 -0
  129. package/dist/presentation/dto/index.js.map +1 -0
  130. package/dist/presentation/dto/model-provider-config.d.ts +6 -0
  131. package/dist/presentation/dto/model-provider-config.d.ts.map +1 -0
  132. package/dist/presentation/dto/model-provider-config.js +32 -0
  133. package/dist/presentation/dto/model-provider-config.js.map +1 -0
  134. package/dist/presentation/e2eRoutes/create-model-provider.d.ts +2 -0
  135. package/dist/presentation/e2eRoutes/create-model-provider.d.ts.map +1 -0
  136. package/dist/presentation/e2eRoutes/create-model-provider.js +66 -0
  137. package/dist/presentation/e2eRoutes/create-model-provider.js.map +1 -0
  138. package/dist/presentation/e2eRoutes/index.d.ts +3 -0
  139. package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
  140. package/dist/presentation/e2eRoutes/index.js +9 -0
  141. package/dist/presentation/e2eRoutes/index.js.map +1 -0
  142. package/dist/presentation/index.d.ts +2 -0
  143. package/dist/presentation/index.d.ts.map +1 -0
  144. package/dist/presentation/index.js +3 -0
  145. package/dist/presentation/index.js.map +1 -0
  146. package/dist/presentation/routes/create-custom-model-provider.d.ts +2 -0
  147. package/dist/presentation/routes/create-custom-model-provider.d.ts.map +1 -0
  148. package/dist/presentation/routes/create-custom-model-provider.js +45 -0
  149. package/dist/presentation/routes/create-custom-model-provider.js.map +1 -0
  150. package/dist/presentation/routes/delete-model-provider-config.d.ts +2 -0
  151. package/dist/presentation/routes/delete-model-provider-config.d.ts.map +1 -0
  152. package/dist/presentation/routes/delete-model-provider-config.js +38 -0
  153. package/dist/presentation/routes/delete-model-provider-config.js.map +1 -0
  154. package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.d.ts +2 -0
  155. package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.d.ts.map +1 -0
  156. package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.js +37 -0
  157. package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.js.map +1 -0
  158. package/dist/presentation/routes/discover-custom-model-provider-models.d.ts +2 -0
  159. package/dist/presentation/routes/discover-custom-model-provider-models.d.ts.map +1 -0
  160. package/dist/presentation/routes/discover-custom-model-provider-models.js +31 -0
  161. package/dist/presentation/routes/discover-custom-model-provider-models.js.map +1 -0
  162. package/dist/presentation/routes/errors.d.ts +2 -0
  163. package/dist/presentation/routes/errors.d.ts.map +1 -0
  164. package/dist/presentation/routes/errors.js +112 -0
  165. package/dist/presentation/routes/errors.js.map +1 -0
  166. package/dist/presentation/routes/index.d.ts +3 -0
  167. package/dist/presentation/routes/index.d.ts.map +1 -0
  168. package/dist/presentation/routes/index.js +39 -0
  169. package/dist/presentation/routes/index.js.map +1 -0
  170. package/dist/presentation/routes/list-model-provider-catalog.d.ts +2 -0
  171. package/dist/presentation/routes/list-model-provider-catalog.d.ts.map +1 -0
  172. package/dist/presentation/routes/list-model-provider-catalog.js +18 -0
  173. package/dist/presentation/routes/list-model-provider-catalog.js.map +1 -0
  174. package/dist/presentation/routes/list-model-provider-configs.d.ts +2 -0
  175. package/dist/presentation/routes/list-model-provider-configs.d.ts.map +1 -0
  176. package/dist/presentation/routes/list-model-provider-configs.js +37 -0
  177. package/dist/presentation/routes/list-model-provider-configs.js.map +1 -0
  178. package/dist/presentation/routes/set-default-harness.d.ts +2 -0
  179. package/dist/presentation/routes/set-default-harness.d.ts.map +1 -0
  180. package/dist/presentation/routes/set-default-harness.js +38 -0
  181. package/dist/presentation/routes/set-default-harness.js.map +1 -0
  182. package/dist/presentation/routes/set-default-model-provider.d.ts +2 -0
  183. package/dist/presentation/routes/set-default-model-provider.d.ts.map +1 -0
  184. package/dist/presentation/routes/set-default-model-provider.js +48 -0
  185. package/dist/presentation/routes/set-default-model-provider.js.map +1 -0
  186. package/dist/presentation/routes/update-custom-model-provider.d.ts +2 -0
  187. package/dist/presentation/routes/update-custom-model-provider.d.ts.map +1 -0
  188. package/dist/presentation/routes/update-custom-model-provider.js +47 -0
  189. package/dist/presentation/routes/update-custom-model-provider.js.map +1 -0
  190. package/dist/presentation/routes/update-model-provider-default-model.d.ts +2 -0
  191. package/dist/presentation/routes/update-model-provider-default-model.d.ts.map +1 -0
  192. package/dist/presentation/routes/update-model-provider-default-model.js +38 -0
  193. package/dist/presentation/routes/update-model-provider-default-model.js.map +1 -0
  194. package/dist/presentation/routes/upsert-model-provider-config.d.ts +2 -0
  195. package/dist/presentation/routes/upsert-model-provider-config.d.ts.map +1 -0
  196. package/dist/presentation/routes/upsert-model-provider-config.js +52 -0
  197. package/dist/presentation/routes/upsert-model-provider-config.js.map +1 -0
  198. package/dist/tsconfig.test.tsbuildinfo +1 -0
  199. package/drizzle/0000_daffy_leopardon.sql +28 -0
  200. package/drizzle/0001_default_harness.sql +1 -0
  201. package/drizzle/meta/0000_snapshot.json +199 -0
  202. package/drizzle/meta/0001_snapshot.json +205 -0
  203. package/drizzle/meta/_journal.json +20 -0
  204. package/drizzle.config.ts +7 -0
  205. package/package.json +109 -0
  206. package/src/config.test.ts +118 -0
  207. package/src/config.ts +75 -0
  208. package/src/core/catalog-registry.test.ts +48 -0
  209. package/src/core/credential-fingerprints.test.ts +34 -0
  210. package/src/core/credential-fingerprints.ts +84 -0
  211. package/src/core/custom-model-provider-config-service.ts +537 -0
  212. package/src/core/custom-provider-url.ts +24 -0
  213. package/src/core/discover-custom-model-provider-models.ts +147 -0
  214. package/src/core/entities/agent-workspace-settings.ts +9 -0
  215. package/src/core/entities/model-provider-config.ts +25 -0
  216. package/src/core/errors.ts +139 -0
  217. package/src/core/harness/claude.ts +75 -0
  218. package/src/core/harness/index.ts +19 -0
  219. package/src/core/harness/pi.ts +18 -0
  220. package/src/core/harness/registry.test.ts +281 -0
  221. package/src/core/harness/registry.ts +108 -0
  222. package/src/core/index.ts +56 -0
  223. package/src/core/model-provider-catalog.ts +33 -0
  224. package/src/core/model-provider-config-service.test.ts +940 -0
  225. package/src/core/model-provider-config-service.ts +207 -0
  226. package/src/core/model-provider-validation.test.ts +354 -0
  227. package/src/core/model-provider-validation.ts +276 -0
  228. package/src/core/resolve-agent-config.test.ts +441 -0
  229. package/src/core/resolve-agent-config.ts +272 -0
  230. package/src/core/resolve-runtime-credentials.test.ts +316 -0
  231. package/src/core/resolve-runtime-credentials.ts +147 -0
  232. package/src/core/workspace-agent-defaults-resolver.ts +42 -0
  233. package/src/db/agent-defaults-snapshot.ts +49 -0
  234. package/src/db/agent-workspace-settings.test.ts +111 -0
  235. package/src/db/agent-workspace-settings.ts +92 -0
  236. package/src/db/db.ts +20 -0
  237. package/src/db/index.ts +29 -0
  238. package/src/db/model-provider-configs.test.ts +281 -0
  239. package/src/db/model-provider-configs.ts +230 -0
  240. package/src/db/schema/agent-workspace-settings.ts +27 -0
  241. package/src/db/schema/common.ts +3 -0
  242. package/src/db/schema/model-provider-configs.ts +80 -0
  243. package/src/index.ts +43 -0
  244. package/src/metrics/index.ts +1 -0
  245. package/src/metrics/instance.ts +18 -0
  246. package/src/presentation/dto/index.ts +5 -0
  247. package/src/presentation/dto/model-provider-config.ts +41 -0
  248. package/src/presentation/e2eRoutes/create-model-provider.test.ts +47 -0
  249. package/src/presentation/e2eRoutes/create-model-provider.ts +61 -0
  250. package/src/presentation/e2eRoutes/index.ts +7 -0
  251. package/src/presentation/index.ts +1 -0
  252. package/src/presentation/routes/create-custom-model-provider.ts +45 -0
  253. package/src/presentation/routes/delete-model-provider-config.ts +31 -0
  254. package/src/presentation/routes/discover-custom-model-provider-models-by-slug.ts +42 -0
  255. package/src/presentation/routes/discover-custom-model-provider-models.ts +29 -0
  256. package/src/presentation/routes/errors.ts +122 -0
  257. package/src/presentation/routes/index.ts +37 -0
  258. package/src/presentation/routes/list-model-provider-catalog.ts +15 -0
  259. package/src/presentation/routes/list-model-provider-configs.ts +33 -0
  260. package/src/presentation/routes/model-provider-catalog.test.ts +73 -0
  261. package/src/presentation/routes/model-provider-config.test.ts +807 -0
  262. package/src/presentation/routes/set-default-harness.ts +33 -0
  263. package/src/presentation/routes/set-default-model-provider.ts +47 -0
  264. package/src/presentation/routes/update-custom-model-provider.ts +50 -0
  265. package/src/presentation/routes/update-model-provider-default-model.ts +40 -0
  266. package/src/presentation/routes/upsert-model-provider-config.ts +53 -0
  267. package/test/env.ts +8 -0
  268. package/test/globalSetup.ts +25 -0
  269. package/test/setup.ts +17 -0
  270. package/tsconfig.build.json +9 -0
  271. package/tsconfig.build.tsbuildinfo +1 -0
  272. package/tsconfig.json +3 -0
  273. package/tsconfig.test.json +8 -0
  274. package/vitest.config.ts +11 -0
@@ -0,0 +1,390 @@
1
+ import { DEFAULT_AGENT_THINKING } from '@shipfox/api-agent-dto';
2
+ import { deleteSecrets, getSecretsByNamespace, setSecrets } from '@shipfox/api-secrets';
3
+ import { assertEgressAllowed } from '@shipfox/node-egress-guard';
4
+ import { deleteModelProviderConfig, getModelProviderConfig, insertCustomModelProviderConfig, upsertModelProviderConfig } from '#db/index.js';
5
+ import { agentSystemNamespace, customCredentialsToStoreValues, customCredentialToStoreKey, storeValuesToCustomRuntimeCredentials } from './credential-fingerprints.js';
6
+ import { normalizeCustomProviderBaseUrl } from './custom-provider-url.js';
7
+ import { CustomModelProviderConfigNotFoundError, CustomModelProviderSlugCollisionError, CustomModelProviderStoredSecretBaseUrlChangeError, InvalidAgentModelError, InvalidCustomModelProviderHeaderKeepError } from './errors.js';
8
+ import { egressPolicy, probeCustomModelProviderCredentials, runProviderProbe } from './model-provider-validation.js';
9
+ export async function createCustomModelProviderConfig(params, options = {}) {
10
+ const probe = options.probe ?? probeCustomModelProviderCredentials;
11
+ const existing = await getModelProviderConfig({
12
+ workspaceId: params.workspaceId,
13
+ providerId: params.body.slug
14
+ });
15
+ if (existing !== undefined) {
16
+ throw new CustomModelProviderSlugCollisionError(params.workspaceId, params.body.slug);
17
+ }
18
+ const splitHeaders = splitCustomHeaders(params.body.headers ?? []);
19
+ const secretCredentials = {
20
+ ...params.body.api_key ? {
21
+ api_key: params.body.api_key
22
+ } : {},
23
+ ...splitHeaders.secretCredentials
24
+ };
25
+ const probeModel = selectProbeModel(params.body.models, params.body.default_model ?? null);
26
+ await assertEgressAllowed(params.body.base_url, egressPolicy());
27
+ await runProviderProbe({
28
+ probe,
29
+ args: {
30
+ providerId: params.body.slug,
31
+ api: params.body.api,
32
+ baseUrl: params.body.base_url,
33
+ model: probeModel,
34
+ apiKey: params.body.api_key,
35
+ headers: splitHeaders.runtimeHeaders,
36
+ ...params.signal ? {
37
+ signal: params.signal
38
+ } : {}
39
+ },
40
+ metricLabel: 'custom',
41
+ providerId: params.body.slug,
42
+ secrets: Object.values(secretCredentials),
43
+ signal: params.signal
44
+ });
45
+ const config = await insertCustomModelProviderConfig({
46
+ workspaceId: params.workspaceId,
47
+ providerId: params.body.slug,
48
+ kind: 'custom',
49
+ displayName: params.body.display_name,
50
+ api: params.body.api,
51
+ baseUrl: params.body.base_url,
52
+ headers: splitHeaders.plaintextHeaders,
53
+ secretHeaderNames: splitHeaders.secretHeaderNames,
54
+ models: params.body.models,
55
+ requiresApiKey: params.body.api_key !== undefined,
56
+ defaultModel: params.body.default_model ?? null,
57
+ defaultThinking: DEFAULT_AGENT_THINKING,
58
+ setAsDefault: params.setAsDefault
59
+ });
60
+ if (!config) {
61
+ throw new CustomModelProviderSlugCollisionError(params.workspaceId, params.body.slug);
62
+ }
63
+ try {
64
+ await setSecrets({
65
+ workspaceId: params.workspaceId,
66
+ namespace: agentSystemNamespace(params.body.slug),
67
+ values: customCredentialsToStoreValues(secretCredentials)
68
+ });
69
+ return config;
70
+ } catch (error) {
71
+ await deleteModelProviderConfig({
72
+ workspaceId: params.workspaceId,
73
+ providerId: params.body.slug
74
+ });
75
+ await deleteSecrets({
76
+ workspaceId: params.workspaceId,
77
+ namespace: agentSystemNamespace(params.body.slug)
78
+ });
79
+ throw error;
80
+ }
81
+ }
82
+ export async function updateCustomModelProviderConfig(params, options = {}) {
83
+ const probe = options.probe ?? probeCustomModelProviderCredentials;
84
+ const existing = await getExistingCustomConfig(params.workspaceId, params.providerId);
85
+ const existingSecrets = await getCustomProviderSecrets({
86
+ workspaceId: params.workspaceId,
87
+ providerId: params.providerId
88
+ });
89
+ const nextModels = params.body.models ?? existing.models ?? [];
90
+ const nextDefaultModel = resolveNextDefaultModel(params.providerId, existing.defaultModel, nextModels, params.body);
91
+ const nextApi = params.body.api ?? existing.api ?? 'openai-responses';
92
+ const nextBaseUrl = params.body.base_url ?? existing.baseUrl ?? '';
93
+ const nextDisplayName = params.body.display_name ?? existing.displayName ?? params.providerId;
94
+ assertStoredSecretsNotReusedAfterBaseUrlChange({
95
+ body: params.body,
96
+ existing,
97
+ existingSecrets,
98
+ providerId: params.providerId
99
+ });
100
+ const nextHeaders = resolveNextHeaders({
101
+ requestHeaders: params.body.headers,
102
+ existing,
103
+ existingSecrets,
104
+ providerId: params.providerId
105
+ });
106
+ const nextApiKey = params.body.api_key ?? existingSecrets.api_key;
107
+ const newSecretCredentials = {
108
+ ...params.body.api_key ? {
109
+ api_key: params.body.api_key
110
+ } : {},
111
+ ...nextHeaders.newSecretCredentials
112
+ };
113
+ const probeSecretCredentials = {
114
+ ...nextApiKey ? {
115
+ api_key: nextApiKey
116
+ } : {},
117
+ ...nextHeaders.probeSecretCredentials
118
+ };
119
+ await assertEgressAllowed(nextBaseUrl, egressPolicy());
120
+ await runProviderProbe({
121
+ probe,
122
+ args: {
123
+ providerId: params.providerId,
124
+ api: nextApi,
125
+ baseUrl: nextBaseUrl,
126
+ model: selectProbeModel(nextModels, nextDefaultModel),
127
+ apiKey: nextApiKey,
128
+ headers: nextHeaders.runtimeHeaders,
129
+ ...params.signal ? {
130
+ signal: params.signal
131
+ } : {}
132
+ },
133
+ metricLabel: 'custom',
134
+ providerId: params.providerId,
135
+ secrets: Object.values(probeSecretCredentials),
136
+ signal: params.signal
137
+ });
138
+ const namespace = agentSystemNamespace(params.providerId);
139
+ await setSecrets({
140
+ workspaceId: params.workspaceId,
141
+ namespace,
142
+ values: customCredentialsToStoreValues(newSecretCredentials)
143
+ });
144
+ let updated;
145
+ try {
146
+ updated = await upsertModelProviderConfig({
147
+ workspaceId: params.workspaceId,
148
+ providerId: params.providerId,
149
+ kind: 'custom',
150
+ displayName: nextDisplayName,
151
+ api: nextApi,
152
+ baseUrl: nextBaseUrl,
153
+ headers: nextHeaders.plaintextHeaders,
154
+ secretHeaderNames: nextHeaders.secretHeaderNames,
155
+ models: nextModels,
156
+ requiresApiKey: existing.requiresApiKey || params.body.api_key !== undefined,
157
+ defaultModel: nextDefaultModel,
158
+ defaultThinking: existing.defaultThinking
159
+ });
160
+ } catch (error) {
161
+ await rollbackCustomProviderSecretUpdate({
162
+ workspaceId: params.workspaceId,
163
+ namespace,
164
+ existingSecrets,
165
+ attemptedSecrets: newSecretCredentials
166
+ }).catch(()=>undefined);
167
+ throw error;
168
+ }
169
+ await pruneStaleCustomHeaderSecrets({
170
+ workspaceId: params.workspaceId,
171
+ providerId: params.providerId,
172
+ existingSecrets,
173
+ newSecrets: newSecretCredentials,
174
+ replaceHeaders: params.body.headers !== undefined
175
+ });
176
+ return updated;
177
+ }
178
+ export async function resolveCustomModelProviderDiscoveryParams(params) {
179
+ const existing = await getExistingCustomConfig(params.workspaceId, params.providerId);
180
+ const existingSecrets = await getCustomProviderSecrets({
181
+ workspaceId: params.workspaceId,
182
+ providerId: params.providerId
183
+ });
184
+ assertStoredSecretsNotReusedAfterBaseUrlChange({
185
+ body: params.body,
186
+ existing,
187
+ existingSecrets,
188
+ providerId: params.providerId
189
+ });
190
+ const headers = resolveNextHeaders({
191
+ requestHeaders: params.body.headers,
192
+ existing,
193
+ existingSecrets,
194
+ providerId: params.providerId
195
+ });
196
+ return {
197
+ api: params.body.api ?? existing.api ?? 'openai-responses',
198
+ base_url: params.body.base_url ?? existing.baseUrl ?? '',
199
+ ...params.body.api_key ?? existingSecrets.api_key ? {
200
+ api_key: params.body.api_key ?? existingSecrets.api_key
201
+ } : {},
202
+ headers: Object.entries(headers.runtimeHeaders).map(([name, value])=>({
203
+ name,
204
+ value
205
+ }))
206
+ };
207
+ }
208
+ async function getExistingCustomConfig(workspaceId, providerId) {
209
+ const existing = await getModelProviderConfig({
210
+ workspaceId,
211
+ providerId
212
+ });
213
+ if (existing?.kind !== 'custom') {
214
+ throw new CustomModelProviderConfigNotFoundError(workspaceId, providerId);
215
+ }
216
+ return existing;
217
+ }
218
+ function splitCustomHeaders(headers) {
219
+ const plaintextHeaders = [];
220
+ const secretHeaderNames = [];
221
+ const secretCredentials = {};
222
+ const runtimeHeaders = {};
223
+ for (const header of headers){
224
+ runtimeHeaders[header.name] = header.value;
225
+ if (header.secret) {
226
+ secretHeaderNames.push(header.name);
227
+ secretCredentials[`header:${header.name}`] = header.value;
228
+ } else {
229
+ plaintextHeaders.push({
230
+ name: header.name,
231
+ value: header.value
232
+ });
233
+ }
234
+ }
235
+ return {
236
+ plaintextHeaders,
237
+ secretHeaderNames,
238
+ secretCredentials,
239
+ runtimeHeaders
240
+ };
241
+ }
242
+ function resolveNextHeaders(params) {
243
+ if (params.requestHeaders !== undefined) {
244
+ return splitUpdateHeaders({
245
+ requestHeaders: params.requestHeaders,
246
+ existingSecrets: params.existingSecrets,
247
+ providerId: params.providerId
248
+ });
249
+ }
250
+ const runtimeHeaders = {};
251
+ for (const header of params.existing.headers ?? []){
252
+ runtimeHeaders[header.name] = header.value;
253
+ }
254
+ const probeSecretCredentials = {};
255
+ for (const [key, value] of Object.entries(params.existingSecrets)){
256
+ if (!key.startsWith('header:')) continue;
257
+ const name = key.slice('header:'.length);
258
+ runtimeHeaders[name] = value;
259
+ probeSecretCredentials[key] = value;
260
+ }
261
+ return {
262
+ plaintextHeaders: params.existing.headers ?? [],
263
+ secretHeaderNames: params.existing.secretHeaderNames ?? secretHeaderNamesFromCredentials(params.existingSecrets),
264
+ runtimeHeaders,
265
+ probeSecretCredentials,
266
+ newSecretCredentials: {}
267
+ };
268
+ }
269
+ function splitUpdateHeaders(params) {
270
+ const plaintextHeaders = [];
271
+ const secretHeaderNames = [];
272
+ const runtimeHeaders = {};
273
+ const probeSecretCredentials = {};
274
+ const newSecretCredentials = {};
275
+ for (const header of params.requestHeaders){
276
+ if (header.keep === true) {
277
+ const key = `header:${header.name}`;
278
+ const value = params.existingSecrets[key];
279
+ if (value === undefined) {
280
+ throw new InvalidCustomModelProviderHeaderKeepError(params.providerId, header.name);
281
+ }
282
+ runtimeHeaders[header.name] = value;
283
+ secretHeaderNames.push(header.name);
284
+ probeSecretCredentials[key] = value;
285
+ newSecretCredentials[key] = value;
286
+ continue;
287
+ }
288
+ const value = header.value;
289
+ if (value === undefined) {
290
+ throw new InvalidCustomModelProviderHeaderKeepError(params.providerId, header.name);
291
+ }
292
+ runtimeHeaders[header.name] = value;
293
+ if (header.secret) {
294
+ const key = `header:${header.name}`;
295
+ secretHeaderNames.push(header.name);
296
+ probeSecretCredentials[key] = value;
297
+ newSecretCredentials[key] = value;
298
+ } else {
299
+ plaintextHeaders.push({
300
+ name: header.name,
301
+ value
302
+ });
303
+ }
304
+ }
305
+ return {
306
+ plaintextHeaders,
307
+ secretHeaderNames,
308
+ runtimeHeaders,
309
+ probeSecretCredentials,
310
+ newSecretCredentials
311
+ };
312
+ }
313
+ function resolveNextDefaultModel(providerId, existingDefaultModel, models, body) {
314
+ if (!('default_model' in body)) {
315
+ if (existingDefaultModel === null) return null;
316
+ return models.some((model)=>model.id === existingDefaultModel) ? existingDefaultModel : null;
317
+ }
318
+ const requestedDefaultModel = body.default_model ?? null;
319
+ if (requestedDefaultModel === null) return null;
320
+ if (models.some((model)=>model.id === requestedDefaultModel)) return requestedDefaultModel;
321
+ throw new InvalidAgentModelError('pi', providerId, requestedDefaultModel);
322
+ }
323
+ function selectProbeModel(models, defaultModel) {
324
+ const model = models.find((candidate)=>candidate.id === defaultModel) ?? models[0];
325
+ if (!model) throw new Error('Custom model provider must include at least one model.');
326
+ return model;
327
+ }
328
+ async function getCustomProviderSecrets(params) {
329
+ const values = await getSecretsByNamespace({
330
+ workspaceId: params.workspaceId,
331
+ namespace: agentSystemNamespace(params.providerId)
332
+ });
333
+ return storeValuesToCustomRuntimeCredentials(values);
334
+ }
335
+ function assertStoredSecretsNotReusedAfterBaseUrlChange(params) {
336
+ if (params.body.base_url === undefined || normalizedUrl(params.body.base_url) === normalizedUrl(params.existing.baseUrl ?? '')) {
337
+ return;
338
+ }
339
+ const reusesStoredApiKey = params.existingSecrets.api_key !== undefined && params.body.api_key === undefined;
340
+ const reusesStoredSecretHeader = params.body.headers === undefined ? Object.keys(params.existingSecrets).some((key)=>key.startsWith('header:')) : params.body.headers.some((header)=>header.keep === true);
341
+ if (reusesStoredApiKey || reusesStoredSecretHeader) {
342
+ throw new CustomModelProviderStoredSecretBaseUrlChangeError(params.providerId);
343
+ }
344
+ }
345
+ function normalizedUrl(value) {
346
+ return normalizeCustomProviderBaseUrl(value);
347
+ }
348
+ async function rollbackCustomProviderSecretUpdate(params) {
349
+ const attemptedKeys = Object.keys(params.attemptedSecrets);
350
+ if (attemptedKeys.length === 0) return;
351
+ const secretsToRestore = Object.fromEntries(attemptedKeys.flatMap((key)=>{
352
+ const value = params.existingSecrets[key];
353
+ return value === undefined ? [] : [
354
+ [
355
+ key,
356
+ value
357
+ ]
358
+ ];
359
+ }));
360
+ if (Object.keys(secretsToRestore).length > 0) {
361
+ await setSecrets({
362
+ workspaceId: params.workspaceId,
363
+ namespace: params.namespace,
364
+ values: customCredentialsToStoreValues(secretsToRestore)
365
+ });
366
+ }
367
+ const newKeys = attemptedKeys.filter((key)=>params.existingSecrets[key] === undefined);
368
+ if (newKeys.length === 0) return;
369
+ await deleteSecrets({
370
+ workspaceId: params.workspaceId,
371
+ namespace: params.namespace,
372
+ keys: newKeys.map(customCredentialToStoreKey)
373
+ });
374
+ }
375
+ async function pruneStaleCustomHeaderSecrets(params) {
376
+ if (!params.replaceHeaders) return;
377
+ const nextKeys = new Set(Object.keys(params.newSecrets));
378
+ const staleKeys = Object.keys(params.existingSecrets).filter((key)=>key.startsWith('header:') && !nextKeys.has(key));
379
+ if (staleKeys.length === 0) return;
380
+ await deleteSecrets({
381
+ workspaceId: params.workspaceId,
382
+ namespace: agentSystemNamespace(params.providerId),
383
+ keys: staleKeys.map(customCredentialToStoreKey)
384
+ });
385
+ }
386
+ function secretHeaderNamesFromCredentials(credentials) {
387
+ return Object.keys(credentials).filter((key)=>key.startsWith('header:')).map((key)=>key.slice('header:'.length)).sort();
388
+ }
389
+
390
+ //# sourceMappingURL=custom-model-provider-config-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/custom-model-provider-config-service.ts"],"sourcesContent":["import {\n type CreateCustomModelProviderBodyDto,\n type CustomAgentModelDto,\n type CustomModelProviderHeaderDto,\n type CustomModelProviderHeaderRequestDto,\n DEFAULT_AGENT_THINKING,\n type DiscoverCustomModelProviderModelsBodyDto,\n type DiscoverCustomModelProviderModelsBySlugBodyDto,\n type ModelProviderRef,\n type UpdateCustomModelProviderBodyDto,\n type UpdateCustomModelProviderHeaderRequestDto,\n} from '@shipfox/api-agent-dto';\nimport {deleteSecrets, getSecretsByNamespace, setSecrets} from '@shipfox/api-secrets';\nimport {assertEgressAllowed} from '@shipfox/node-egress-guard';\nimport {\n deleteModelProviderConfig,\n getModelProviderConfig,\n insertCustomModelProviderConfig,\n upsertModelProviderConfig,\n} from '#db/index.js';\nimport {\n agentSystemNamespace,\n customCredentialsToStoreValues,\n customCredentialToStoreKey,\n storeValuesToCustomRuntimeCredentials,\n} from './credential-fingerprints.js';\nimport {normalizeCustomProviderBaseUrl} from './custom-provider-url.js';\nimport type {ModelProviderConfig} from './entities/model-provider-config.js';\nimport {\n CustomModelProviderConfigNotFoundError,\n CustomModelProviderSlugCollisionError,\n CustomModelProviderStoredSecretBaseUrlChangeError,\n InvalidAgentModelError,\n InvalidCustomModelProviderHeaderKeepError,\n} from './errors.js';\nimport {\n egressPolicy,\n probeCustomModelProviderCredentials,\n runProviderProbe,\n} from './model-provider-validation.js';\n\nexport interface CreateCustomModelProviderConfigParams {\n workspaceId: string;\n body: CreateCustomModelProviderBodyDto;\n setAsDefault?: boolean | undefined;\n signal?: AbortSignal | undefined;\n}\n\nexport interface UpdateCustomModelProviderConfigParams {\n workspaceId: string;\n providerId: ModelProviderRef;\n body: UpdateCustomModelProviderBodyDto;\n signal?: AbortSignal | undefined;\n}\n\nexport interface CustomModelProviderConfigServiceOptions {\n probe?: typeof probeCustomModelProviderCredentials | undefined;\n}\n\ninterface SplitHeaders {\n plaintextHeaders: CustomModelProviderHeaderDto[];\n secretHeaderNames: string[];\n secretCredentials: Record<string, string>;\n runtimeHeaders: Record<string, string>;\n}\n\nexport async function createCustomModelProviderConfig(\n params: CreateCustomModelProviderConfigParams,\n options: CustomModelProviderConfigServiceOptions = {},\n): Promise<ModelProviderConfig> {\n const probe = options.probe ?? probeCustomModelProviderCredentials;\n\n const existing = await getModelProviderConfig({\n workspaceId: params.workspaceId,\n providerId: params.body.slug,\n });\n if (existing !== undefined) {\n throw new CustomModelProviderSlugCollisionError(params.workspaceId, params.body.slug);\n }\n\n const splitHeaders = splitCustomHeaders(params.body.headers ?? []);\n const secretCredentials = {\n ...(params.body.api_key ? {api_key: params.body.api_key} : {}),\n ...splitHeaders.secretCredentials,\n };\n const probeModel = selectProbeModel(params.body.models, params.body.default_model ?? null);\n\n await assertEgressAllowed(params.body.base_url, egressPolicy());\n await runProviderProbe({\n probe,\n args: {\n providerId: params.body.slug,\n api: params.body.api,\n baseUrl: params.body.base_url,\n model: probeModel,\n apiKey: params.body.api_key,\n headers: splitHeaders.runtimeHeaders,\n ...(params.signal ? {signal: params.signal} : {}),\n },\n metricLabel: 'custom',\n providerId: params.body.slug,\n secrets: Object.values(secretCredentials),\n signal: params.signal,\n });\n\n const config = await insertCustomModelProviderConfig({\n workspaceId: params.workspaceId,\n providerId: params.body.slug,\n kind: 'custom',\n displayName: params.body.display_name,\n api: params.body.api,\n baseUrl: params.body.base_url,\n headers: splitHeaders.plaintextHeaders,\n secretHeaderNames: splitHeaders.secretHeaderNames,\n models: params.body.models,\n requiresApiKey: params.body.api_key !== undefined,\n defaultModel: params.body.default_model ?? null,\n defaultThinking: DEFAULT_AGENT_THINKING,\n setAsDefault: params.setAsDefault,\n });\n if (!config) {\n throw new CustomModelProviderSlugCollisionError(params.workspaceId, params.body.slug);\n }\n\n try {\n await setSecrets({\n workspaceId: params.workspaceId,\n namespace: agentSystemNamespace(params.body.slug),\n values: customCredentialsToStoreValues(secretCredentials),\n });\n return config;\n } catch (error) {\n await deleteModelProviderConfig({\n workspaceId: params.workspaceId,\n providerId: params.body.slug,\n });\n await deleteSecrets({\n workspaceId: params.workspaceId,\n namespace: agentSystemNamespace(params.body.slug),\n });\n throw error;\n }\n}\n\nexport async function updateCustomModelProviderConfig(\n params: UpdateCustomModelProviderConfigParams,\n options: CustomModelProviderConfigServiceOptions = {},\n): Promise<ModelProviderConfig> {\n const probe = options.probe ?? probeCustomModelProviderCredentials;\n\n const existing = await getExistingCustomConfig(params.workspaceId, params.providerId);\n const existingSecrets = await getCustomProviderSecrets({\n workspaceId: params.workspaceId,\n providerId: params.providerId,\n });\n const nextModels = params.body.models ?? existing.models ?? [];\n const nextDefaultModel = resolveNextDefaultModel(\n params.providerId,\n existing.defaultModel,\n nextModels,\n params.body,\n );\n const nextApi = params.body.api ?? existing.api ?? 'openai-responses';\n const nextBaseUrl = params.body.base_url ?? existing.baseUrl ?? '';\n const nextDisplayName = params.body.display_name ?? existing.displayName ?? params.providerId;\n assertStoredSecretsNotReusedAfterBaseUrlChange({\n body: params.body,\n existing,\n existingSecrets,\n providerId: params.providerId,\n });\n const nextHeaders = resolveNextHeaders({\n requestHeaders: params.body.headers,\n existing,\n existingSecrets,\n providerId: params.providerId,\n });\n const nextApiKey = params.body.api_key ?? existingSecrets.api_key;\n const newSecretCredentials = {\n ...(params.body.api_key ? {api_key: params.body.api_key} : {}),\n ...nextHeaders.newSecretCredentials,\n };\n const probeSecretCredentials = {\n ...(nextApiKey ? {api_key: nextApiKey} : {}),\n ...nextHeaders.probeSecretCredentials,\n };\n\n await assertEgressAllowed(nextBaseUrl, egressPolicy());\n await runProviderProbe({\n probe,\n args: {\n providerId: params.providerId,\n api: nextApi,\n baseUrl: nextBaseUrl,\n model: selectProbeModel(nextModels, nextDefaultModel),\n apiKey: nextApiKey,\n headers: nextHeaders.runtimeHeaders,\n ...(params.signal ? {signal: params.signal} : {}),\n },\n metricLabel: 'custom',\n providerId: params.providerId,\n secrets: Object.values(probeSecretCredentials),\n signal: params.signal,\n });\n\n const namespace = agentSystemNamespace(params.providerId);\n await setSecrets({\n workspaceId: params.workspaceId,\n namespace,\n values: customCredentialsToStoreValues(newSecretCredentials),\n });\n\n let updated: ModelProviderConfig;\n try {\n updated = await upsertModelProviderConfig({\n workspaceId: params.workspaceId,\n providerId: params.providerId,\n kind: 'custom',\n displayName: nextDisplayName,\n api: nextApi,\n baseUrl: nextBaseUrl,\n headers: nextHeaders.plaintextHeaders,\n secretHeaderNames: nextHeaders.secretHeaderNames,\n models: nextModels,\n requiresApiKey: existing.requiresApiKey || params.body.api_key !== undefined,\n defaultModel: nextDefaultModel,\n defaultThinking: existing.defaultThinking,\n });\n } catch (error) {\n await rollbackCustomProviderSecretUpdate({\n workspaceId: params.workspaceId,\n namespace,\n existingSecrets,\n attemptedSecrets: newSecretCredentials,\n }).catch(() => undefined);\n throw error;\n }\n\n await pruneStaleCustomHeaderSecrets({\n workspaceId: params.workspaceId,\n providerId: params.providerId,\n existingSecrets,\n newSecrets: newSecretCredentials,\n replaceHeaders: params.body.headers !== undefined,\n });\n\n return updated;\n}\n\nexport async function resolveCustomModelProviderDiscoveryParams(params: {\n workspaceId: string;\n providerId: ModelProviderRef;\n body: DiscoverCustomModelProviderModelsBySlugBodyDto;\n}): Promise<DiscoverCustomModelProviderModelsBodyDto> {\n const existing = await getExistingCustomConfig(params.workspaceId, params.providerId);\n const existingSecrets = await getCustomProviderSecrets({\n workspaceId: params.workspaceId,\n providerId: params.providerId,\n });\n assertStoredSecretsNotReusedAfterBaseUrlChange({\n body: params.body,\n existing,\n existingSecrets,\n providerId: params.providerId,\n });\n const headers = resolveNextHeaders({\n requestHeaders: params.body.headers,\n existing,\n existingSecrets,\n providerId: params.providerId,\n });\n\n return {\n api: params.body.api ?? existing.api ?? 'openai-responses',\n base_url: params.body.base_url ?? existing.baseUrl ?? '',\n ...((params.body.api_key ?? existingSecrets.api_key)\n ? {api_key: params.body.api_key ?? existingSecrets.api_key}\n : {}),\n headers: Object.entries(headers.runtimeHeaders).map(([name, value]) => ({name, value})),\n };\n}\n\nasync function getExistingCustomConfig(\n workspaceId: string,\n providerId: ModelProviderRef,\n): Promise<ModelProviderConfig> {\n const existing = await getModelProviderConfig({workspaceId, providerId});\n if (existing?.kind !== 'custom') {\n throw new CustomModelProviderConfigNotFoundError(workspaceId, providerId);\n }\n return existing;\n}\n\nfunction splitCustomHeaders(headers: CustomModelProviderHeaderRequestDto[]): SplitHeaders {\n const plaintextHeaders: CustomModelProviderHeaderDto[] = [];\n const secretHeaderNames: string[] = [];\n const secretCredentials: Record<string, string> = {};\n const runtimeHeaders: Record<string, string> = {};\n\n for (const header of headers) {\n runtimeHeaders[header.name] = header.value;\n if (header.secret) {\n secretHeaderNames.push(header.name);\n secretCredentials[`header:${header.name}`] = header.value;\n } else {\n plaintextHeaders.push({name: header.name, value: header.value});\n }\n }\n\n return {plaintextHeaders, secretHeaderNames, secretCredentials, runtimeHeaders};\n}\n\nfunction resolveNextHeaders(params: {\n requestHeaders: UpdateCustomModelProviderHeaderRequestDto[] | undefined;\n existing: ModelProviderConfig;\n existingSecrets: Record<string, string>;\n providerId: ModelProviderRef;\n}): {\n plaintextHeaders: CustomModelProviderHeaderDto[];\n secretHeaderNames: string[];\n runtimeHeaders: Record<string, string>;\n probeSecretCredentials: Record<string, string>;\n newSecretCredentials: Record<string, string>;\n} {\n if (params.requestHeaders !== undefined) {\n return splitUpdateHeaders({\n requestHeaders: params.requestHeaders,\n existingSecrets: params.existingSecrets,\n providerId: params.providerId,\n });\n }\n\n const runtimeHeaders: Record<string, string> = {};\n for (const header of params.existing.headers ?? []) {\n runtimeHeaders[header.name] = header.value;\n }\n\n const probeSecretCredentials: Record<string, string> = {};\n for (const [key, value] of Object.entries(params.existingSecrets)) {\n if (!key.startsWith('header:')) continue;\n const name = key.slice('header:'.length);\n runtimeHeaders[name] = value;\n probeSecretCredentials[key] = value;\n }\n\n return {\n plaintextHeaders: params.existing.headers ?? [],\n secretHeaderNames:\n params.existing.secretHeaderNames ?? secretHeaderNamesFromCredentials(params.existingSecrets),\n runtimeHeaders,\n probeSecretCredentials,\n newSecretCredentials: {},\n };\n}\n\nfunction splitUpdateHeaders(params: {\n requestHeaders: UpdateCustomModelProviderHeaderRequestDto[];\n existingSecrets: Record<string, string>;\n providerId: ModelProviderRef;\n}): {\n plaintextHeaders: CustomModelProviderHeaderDto[];\n secretHeaderNames: string[];\n runtimeHeaders: Record<string, string>;\n probeSecretCredentials: Record<string, string>;\n newSecretCredentials: Record<string, string>;\n} {\n const plaintextHeaders: CustomModelProviderHeaderDto[] = [];\n const secretHeaderNames: string[] = [];\n const runtimeHeaders: Record<string, string> = {};\n const probeSecretCredentials: Record<string, string> = {};\n const newSecretCredentials: Record<string, string> = {};\n\n for (const header of params.requestHeaders) {\n if (header.keep === true) {\n const key = `header:${header.name}`;\n const value = params.existingSecrets[key];\n if (value === undefined) {\n throw new InvalidCustomModelProviderHeaderKeepError(params.providerId, header.name);\n }\n runtimeHeaders[header.name] = value;\n secretHeaderNames.push(header.name);\n probeSecretCredentials[key] = value;\n newSecretCredentials[key] = value;\n continue;\n }\n\n const value = header.value;\n if (value === undefined) {\n throw new InvalidCustomModelProviderHeaderKeepError(params.providerId, header.name);\n }\n runtimeHeaders[header.name] = value;\n if (header.secret) {\n const key = `header:${header.name}`;\n secretHeaderNames.push(header.name);\n probeSecretCredentials[key] = value;\n newSecretCredentials[key] = value;\n } else {\n plaintextHeaders.push({name: header.name, value});\n }\n }\n\n return {\n plaintextHeaders,\n secretHeaderNames,\n runtimeHeaders,\n probeSecretCredentials,\n newSecretCredentials,\n };\n}\n\nfunction resolveNextDefaultModel(\n providerId: ModelProviderRef,\n existingDefaultModel: string | null,\n models: CustomAgentModelDto[],\n body: UpdateCustomModelProviderBodyDto,\n): string | null {\n if (!('default_model' in body)) {\n if (existingDefaultModel === null) return null;\n return models.some((model) => model.id === existingDefaultModel) ? existingDefaultModel : null;\n }\n\n const requestedDefaultModel = body.default_model ?? null;\n if (requestedDefaultModel === null) return null;\n if (models.some((model) => model.id === requestedDefaultModel)) return requestedDefaultModel;\n throw new InvalidAgentModelError('pi', providerId, requestedDefaultModel);\n}\n\nfunction selectProbeModel(\n models: CustomAgentModelDto[],\n defaultModel: string | null,\n): CustomAgentModelDto {\n const model = models.find((candidate) => candidate.id === defaultModel) ?? models[0];\n if (!model) throw new Error('Custom model provider must include at least one model.');\n return model;\n}\n\nasync function getCustomProviderSecrets(params: {\n workspaceId: string;\n providerId: ModelProviderRef;\n}): Promise<Record<string, string>> {\n const values = await getSecretsByNamespace({\n workspaceId: params.workspaceId,\n namespace: agentSystemNamespace(params.providerId),\n });\n return storeValuesToCustomRuntimeCredentials(values);\n}\n\nfunction assertStoredSecretsNotReusedAfterBaseUrlChange(params: {\n body: UpdateCustomModelProviderBodyDto | DiscoverCustomModelProviderModelsBySlugBodyDto;\n existing: ModelProviderConfig;\n existingSecrets: Record<string, string>;\n providerId: ModelProviderRef;\n}): void {\n if (\n params.body.base_url === undefined ||\n normalizedUrl(params.body.base_url) === normalizedUrl(params.existing.baseUrl ?? '')\n ) {\n return;\n }\n\n const reusesStoredApiKey =\n params.existingSecrets.api_key !== undefined && params.body.api_key === undefined;\n const reusesStoredSecretHeader =\n params.body.headers === undefined\n ? Object.keys(params.existingSecrets).some((key) => key.startsWith('header:'))\n : params.body.headers.some((header) => header.keep === true);\n\n if (reusesStoredApiKey || reusesStoredSecretHeader) {\n throw new CustomModelProviderStoredSecretBaseUrlChangeError(params.providerId);\n }\n}\n\nfunction normalizedUrl(value: string): string {\n return normalizeCustomProviderBaseUrl(value);\n}\n\nasync function rollbackCustomProviderSecretUpdate(params: {\n workspaceId: string;\n namespace: string;\n existingSecrets: Record<string, string>;\n attemptedSecrets: Record<string, string>;\n}): Promise<void> {\n const attemptedKeys = Object.keys(params.attemptedSecrets);\n if (attemptedKeys.length === 0) return;\n\n const secretsToRestore = Object.fromEntries(\n attemptedKeys.flatMap((key) => {\n const value = params.existingSecrets[key];\n return value === undefined ? [] : [[key, value]];\n }),\n );\n if (Object.keys(secretsToRestore).length > 0) {\n await setSecrets({\n workspaceId: params.workspaceId,\n namespace: params.namespace,\n values: customCredentialsToStoreValues(secretsToRestore),\n });\n }\n\n const newKeys = attemptedKeys.filter((key) => params.existingSecrets[key] === undefined);\n if (newKeys.length === 0) return;\n\n await deleteSecrets({\n workspaceId: params.workspaceId,\n namespace: params.namespace,\n keys: newKeys.map(customCredentialToStoreKey),\n });\n}\n\nasync function pruneStaleCustomHeaderSecrets(params: {\n workspaceId: string;\n providerId: ModelProviderRef;\n existingSecrets: Record<string, string>;\n newSecrets: Record<string, string>;\n replaceHeaders: boolean;\n}): Promise<void> {\n if (!params.replaceHeaders) return;\n\n const nextKeys = new Set(Object.keys(params.newSecrets));\n const staleKeys = Object.keys(params.existingSecrets).filter(\n (key) => key.startsWith('header:') && !nextKeys.has(key),\n );\n if (staleKeys.length === 0) return;\n\n await deleteSecrets({\n workspaceId: params.workspaceId,\n namespace: agentSystemNamespace(params.providerId),\n keys: staleKeys.map(customCredentialToStoreKey),\n });\n}\n\nfunction secretHeaderNamesFromCredentials(credentials: Record<string, string>): string[] {\n return Object.keys(credentials)\n .filter((key) => key.startsWith('header:'))\n .map((key) => key.slice('header:'.length))\n .sort();\n}\n"],"names":["DEFAULT_AGENT_THINKING","deleteSecrets","getSecretsByNamespace","setSecrets","assertEgressAllowed","deleteModelProviderConfig","getModelProviderConfig","insertCustomModelProviderConfig","upsertModelProviderConfig","agentSystemNamespace","customCredentialsToStoreValues","customCredentialToStoreKey","storeValuesToCustomRuntimeCredentials","normalizeCustomProviderBaseUrl","CustomModelProviderConfigNotFoundError","CustomModelProviderSlugCollisionError","CustomModelProviderStoredSecretBaseUrlChangeError","InvalidAgentModelError","InvalidCustomModelProviderHeaderKeepError","egressPolicy","probeCustomModelProviderCredentials","runProviderProbe","createCustomModelProviderConfig","params","options","probe","existing","workspaceId","providerId","body","slug","undefined","splitHeaders","splitCustomHeaders","headers","secretCredentials","api_key","probeModel","selectProbeModel","models","default_model","base_url","args","api","baseUrl","model","apiKey","runtimeHeaders","signal","metricLabel","secrets","Object","values","config","kind","displayName","display_name","plaintextHeaders","secretHeaderNames","requiresApiKey","defaultModel","defaultThinking","setAsDefault","namespace","error","updateCustomModelProviderConfig","getExistingCustomConfig","existingSecrets","getCustomProviderSecrets","nextModels","nextDefaultModel","resolveNextDefaultModel","nextApi","nextBaseUrl","nextDisplayName","assertStoredSecretsNotReusedAfterBaseUrlChange","nextHeaders","resolveNextHeaders","requestHeaders","nextApiKey","newSecretCredentials","probeSecretCredentials","updated","rollbackCustomProviderSecretUpdate","attemptedSecrets","catch","pruneStaleCustomHeaderSecrets","newSecrets","replaceHeaders","resolveCustomModelProviderDiscoveryParams","entries","map","name","value","header","secret","push","splitUpdateHeaders","key","startsWith","slice","length","secretHeaderNamesFromCredentials","keep","existingDefaultModel","some","id","requestedDefaultModel","find","candidate","Error","normalizedUrl","reusesStoredApiKey","reusesStoredSecretHeader","keys","attemptedKeys","secretsToRestore","fromEntries","flatMap","newKeys","filter","nextKeys","Set","staleKeys","has","credentials","sort"],"mappings":"AAAA,SAKEA,sBAAsB,QAMjB,yBAAyB;AAChC,SAAQC,aAAa,EAAEC,qBAAqB,EAAEC,UAAU,QAAO,uBAAuB;AACtF,SAAQC,mBAAmB,QAAO,6BAA6B;AAC/D,SACEC,yBAAyB,EACzBC,sBAAsB,EACtBC,+BAA+B,EAC/BC,yBAAyB,QACpB,eAAe;AACtB,SACEC,oBAAoB,EACpBC,8BAA8B,EAC9BC,0BAA0B,EAC1BC,qCAAqC,QAChC,+BAA+B;AACtC,SAAQC,8BAA8B,QAAO,2BAA2B;AAExE,SACEC,sCAAsC,EACtCC,qCAAqC,EACrCC,iDAAiD,EACjDC,sBAAsB,EACtBC,yCAAyC,QACpC,cAAc;AACrB,SACEC,YAAY,EACZC,mCAAmC,EACnCC,gBAAgB,QACX,iCAAiC;AA2BxC,OAAO,eAAeC,gCACpBC,MAA6C,EAC7CC,UAAmD,CAAC,CAAC;IAErD,MAAMC,QAAQD,QAAQC,KAAK,IAAIL;IAE/B,MAAMM,WAAW,MAAMpB,uBAAuB;QAC5CqB,aAAaJ,OAAOI,WAAW;QAC/BC,YAAYL,OAAOM,IAAI,CAACC,IAAI;IAC9B;IACA,IAAIJ,aAAaK,WAAW;QAC1B,MAAM,IAAIhB,sCAAsCQ,OAAOI,WAAW,EAAEJ,OAAOM,IAAI,CAACC,IAAI;IACtF;IAEA,MAAME,eAAeC,mBAAmBV,OAAOM,IAAI,CAACK,OAAO,IAAI,EAAE;IACjE,MAAMC,oBAAoB;QACxB,GAAIZ,OAAOM,IAAI,CAACO,OAAO,GAAG;YAACA,SAASb,OAAOM,IAAI,CAACO,OAAO;QAAA,IAAI,CAAC,CAAC;QAC7D,GAAGJ,aAAaG,iBAAiB;IACnC;IACA,MAAME,aAAaC,iBAAiBf,OAAOM,IAAI,CAACU,MAAM,EAAEhB,OAAOM,IAAI,CAACW,aAAa,IAAI;IAErF,MAAMpC,oBAAoBmB,OAAOM,IAAI,CAACY,QAAQ,EAAEtB;IAChD,MAAME,iBAAiB;QACrBI;QACAiB,MAAM;YACJd,YAAYL,OAAOM,IAAI,CAACC,IAAI;YAC5Ba,KAAKpB,OAAOM,IAAI,CAACc,GAAG;YACpBC,SAASrB,OAAOM,IAAI,CAACY,QAAQ;YAC7BI,OAAOR;YACPS,QAAQvB,OAAOM,IAAI,CAACO,OAAO;YAC3BF,SAASF,aAAae,cAAc;YACpC,GAAIxB,OAAOyB,MAAM,GAAG;gBAACA,QAAQzB,OAAOyB,MAAM;YAAA,IAAI,CAAC,CAAC;QAClD;QACAC,aAAa;QACbrB,YAAYL,OAAOM,IAAI,CAACC,IAAI;QAC5BoB,SAASC,OAAOC,MAAM,CAACjB;QACvBa,QAAQzB,OAAOyB,MAAM;IACvB;IAEA,MAAMK,SAAS,MAAM9C,gCAAgC;QACnDoB,aAAaJ,OAAOI,WAAW;QAC/BC,YAAYL,OAAOM,IAAI,CAACC,IAAI;QAC5BwB,MAAM;QACNC,aAAahC,OAAOM,IAAI,CAAC2B,YAAY;QACrCb,KAAKpB,OAAOM,IAAI,CAACc,GAAG;QACpBC,SAASrB,OAAOM,IAAI,CAACY,QAAQ;QAC7BP,SAASF,aAAayB,gBAAgB;QACtCC,mBAAmB1B,aAAa0B,iBAAiB;QACjDnB,QAAQhB,OAAOM,IAAI,CAACU,MAAM;QAC1BoB,gBAAgBpC,OAAOM,IAAI,CAACO,OAAO,KAAKL;QACxC6B,cAAcrC,OAAOM,IAAI,CAACW,aAAa,IAAI;QAC3CqB,iBAAiB7D;QACjB8D,cAAcvC,OAAOuC,YAAY;IACnC;IACA,IAAI,CAACT,QAAQ;QACX,MAAM,IAAItC,sCAAsCQ,OAAOI,WAAW,EAAEJ,OAAOM,IAAI,CAACC,IAAI;IACtF;IAEA,IAAI;QACF,MAAM3B,WAAW;YACfwB,aAAaJ,OAAOI,WAAW;YAC/BoC,WAAWtD,qBAAqBc,OAAOM,IAAI,CAACC,IAAI;YAChDsB,QAAQ1C,+BAA+ByB;QACzC;QACA,OAAOkB;IACT,EAAE,OAAOW,OAAO;QACd,MAAM3D,0BAA0B;YAC9BsB,aAAaJ,OAAOI,WAAW;YAC/BC,YAAYL,OAAOM,IAAI,CAACC,IAAI;QAC9B;QACA,MAAM7B,cAAc;YAClB0B,aAAaJ,OAAOI,WAAW;YAC/BoC,WAAWtD,qBAAqBc,OAAOM,IAAI,CAACC,IAAI;QAClD;QACA,MAAMkC;IACR;AACF;AAEA,OAAO,eAAeC,gCACpB1C,MAA6C,EAC7CC,UAAmD,CAAC,CAAC;IAErD,MAAMC,QAAQD,QAAQC,KAAK,IAAIL;IAE/B,MAAMM,WAAW,MAAMwC,wBAAwB3C,OAAOI,WAAW,EAAEJ,OAAOK,UAAU;IACpF,MAAMuC,kBAAkB,MAAMC,yBAAyB;QACrDzC,aAAaJ,OAAOI,WAAW;QAC/BC,YAAYL,OAAOK,UAAU;IAC/B;IACA,MAAMyC,aAAa9C,OAAOM,IAAI,CAACU,MAAM,IAAIb,SAASa,MAAM,IAAI,EAAE;IAC9D,MAAM+B,mBAAmBC,wBACvBhD,OAAOK,UAAU,EACjBF,SAASkC,YAAY,EACrBS,YACA9C,OAAOM,IAAI;IAEb,MAAM2C,UAAUjD,OAAOM,IAAI,CAACc,GAAG,IAAIjB,SAASiB,GAAG,IAAI;IACnD,MAAM8B,cAAclD,OAAOM,IAAI,CAACY,QAAQ,IAAIf,SAASkB,OAAO,IAAI;IAChE,MAAM8B,kBAAkBnD,OAAOM,IAAI,CAAC2B,YAAY,IAAI9B,SAAS6B,WAAW,IAAIhC,OAAOK,UAAU;IAC7F+C,+CAA+C;QAC7C9C,MAAMN,OAAOM,IAAI;QACjBH;QACAyC;QACAvC,YAAYL,OAAOK,UAAU;IAC/B;IACA,MAAMgD,cAAcC,mBAAmB;QACrCC,gBAAgBvD,OAAOM,IAAI,CAACK,OAAO;QACnCR;QACAyC;QACAvC,YAAYL,OAAOK,UAAU;IAC/B;IACA,MAAMmD,aAAaxD,OAAOM,IAAI,CAACO,OAAO,IAAI+B,gBAAgB/B,OAAO;IACjE,MAAM4C,uBAAuB;QAC3B,GAAIzD,OAAOM,IAAI,CAACO,OAAO,GAAG;YAACA,SAASb,OAAOM,IAAI,CAACO,OAAO;QAAA,IAAI,CAAC,CAAC;QAC7D,GAAGwC,YAAYI,oBAAoB;IACrC;IACA,MAAMC,yBAAyB;QAC7B,GAAIF,aAAa;YAAC3C,SAAS2C;QAAU,IAAI,CAAC,CAAC;QAC3C,GAAGH,YAAYK,sBAAsB;IACvC;IAEA,MAAM7E,oBAAoBqE,aAAatD;IACvC,MAAME,iBAAiB;QACrBI;QACAiB,MAAM;YACJd,YAAYL,OAAOK,UAAU;YAC7Be,KAAK6B;YACL5B,SAAS6B;YACT5B,OAAOP,iBAAiB+B,YAAYC;YACpCxB,QAAQiC;YACR7C,SAAS0C,YAAY7B,cAAc;YACnC,GAAIxB,OAAOyB,MAAM,GAAG;gBAACA,QAAQzB,OAAOyB,MAAM;YAAA,IAAI,CAAC,CAAC;QAClD;QACAC,aAAa;QACbrB,YAAYL,OAAOK,UAAU;QAC7BsB,SAASC,OAAOC,MAAM,CAAC6B;QACvBjC,QAAQzB,OAAOyB,MAAM;IACvB;IAEA,MAAMe,YAAYtD,qBAAqBc,OAAOK,UAAU;IACxD,MAAMzB,WAAW;QACfwB,aAAaJ,OAAOI,WAAW;QAC/BoC;QACAX,QAAQ1C,+BAA+BsE;IACzC;IAEA,IAAIE;IACJ,IAAI;QACFA,UAAU,MAAM1E,0BAA0B;YACxCmB,aAAaJ,OAAOI,WAAW;YAC/BC,YAAYL,OAAOK,UAAU;YAC7B0B,MAAM;YACNC,aAAamB;YACb/B,KAAK6B;YACL5B,SAAS6B;YACTvC,SAAS0C,YAAYnB,gBAAgB;YACrCC,mBAAmBkB,YAAYlB,iBAAiB;YAChDnB,QAAQ8B;YACRV,gBAAgBjC,SAASiC,cAAc,IAAIpC,OAAOM,IAAI,CAACO,OAAO,KAAKL;YACnE6B,cAAcU;YACdT,iBAAiBnC,SAASmC,eAAe;QAC3C;IACF,EAAE,OAAOG,OAAO;QACd,MAAMmB,mCAAmC;YACvCxD,aAAaJ,OAAOI,WAAW;YAC/BoC;YACAI;YACAiB,kBAAkBJ;QACpB,GAAGK,KAAK,CAAC,IAAMtD;QACf,MAAMiC;IACR;IAEA,MAAMsB,8BAA8B;QAClC3D,aAAaJ,OAAOI,WAAW;QAC/BC,YAAYL,OAAOK,UAAU;QAC7BuC;QACAoB,YAAYP;QACZQ,gBAAgBjE,OAAOM,IAAI,CAACK,OAAO,KAAKH;IAC1C;IAEA,OAAOmD;AACT;AAEA,OAAO,eAAeO,0CAA0ClE,MAI/D;IACC,MAAMG,WAAW,MAAMwC,wBAAwB3C,OAAOI,WAAW,EAAEJ,OAAOK,UAAU;IACpF,MAAMuC,kBAAkB,MAAMC,yBAAyB;QACrDzC,aAAaJ,OAAOI,WAAW;QAC/BC,YAAYL,OAAOK,UAAU;IAC/B;IACA+C,+CAA+C;QAC7C9C,MAAMN,OAAOM,IAAI;QACjBH;QACAyC;QACAvC,YAAYL,OAAOK,UAAU;IAC/B;IACA,MAAMM,UAAU2C,mBAAmB;QACjCC,gBAAgBvD,OAAOM,IAAI,CAACK,OAAO;QACnCR;QACAyC;QACAvC,YAAYL,OAAOK,UAAU;IAC/B;IAEA,OAAO;QACLe,KAAKpB,OAAOM,IAAI,CAACc,GAAG,IAAIjB,SAASiB,GAAG,IAAI;QACxCF,UAAUlB,OAAOM,IAAI,CAACY,QAAQ,IAAIf,SAASkB,OAAO,IAAI;QACtD,GAAI,AAACrB,OAAOM,IAAI,CAACO,OAAO,IAAI+B,gBAAgB/B,OAAO,GAC/C;YAACA,SAASb,OAAOM,IAAI,CAACO,OAAO,IAAI+B,gBAAgB/B,OAAO;QAAA,IACxD,CAAC,CAAC;QACNF,SAASiB,OAAOuC,OAAO,CAACxD,QAAQa,cAAc,EAAE4C,GAAG,CAAC,CAAC,CAACC,MAAMC,MAAM,GAAM,CAAA;gBAACD;gBAAMC;YAAK,CAAA;IACtF;AACF;AAEA,eAAe3B,wBACbvC,WAAmB,EACnBC,UAA4B;IAE5B,MAAMF,WAAW,MAAMpB,uBAAuB;QAACqB;QAAaC;IAAU;IACtE,IAAIF,UAAU4B,SAAS,UAAU;QAC/B,MAAM,IAAIxC,uCAAuCa,aAAaC;IAChE;IACA,OAAOF;AACT;AAEA,SAASO,mBAAmBC,OAA8C;IACxE,MAAMuB,mBAAmD,EAAE;IAC3D,MAAMC,oBAA8B,EAAE;IACtC,MAAMvB,oBAA4C,CAAC;IACnD,MAAMY,iBAAyC,CAAC;IAEhD,KAAK,MAAM+C,UAAU5D,QAAS;QAC5Ba,cAAc,CAAC+C,OAAOF,IAAI,CAAC,GAAGE,OAAOD,KAAK;QAC1C,IAAIC,OAAOC,MAAM,EAAE;YACjBrC,kBAAkBsC,IAAI,CAACF,OAAOF,IAAI;YAClCzD,iBAAiB,CAAC,CAAC,OAAO,EAAE2D,OAAOF,IAAI,EAAE,CAAC,GAAGE,OAAOD,KAAK;QAC3D,OAAO;YACLpC,iBAAiBuC,IAAI,CAAC;gBAACJ,MAAME,OAAOF,IAAI;gBAAEC,OAAOC,OAAOD,KAAK;YAAA;QAC/D;IACF;IAEA,OAAO;QAACpC;QAAkBC;QAAmBvB;QAAmBY;IAAc;AAChF;AAEA,SAAS8B,mBAAmBtD,MAK3B;IAOC,IAAIA,OAAOuD,cAAc,KAAK/C,WAAW;QACvC,OAAOkE,mBAAmB;YACxBnB,gBAAgBvD,OAAOuD,cAAc;YACrCX,iBAAiB5C,OAAO4C,eAAe;YACvCvC,YAAYL,OAAOK,UAAU;QAC/B;IACF;IAEA,MAAMmB,iBAAyC,CAAC;IAChD,KAAK,MAAM+C,UAAUvE,OAAOG,QAAQ,CAACQ,OAAO,IAAI,EAAE,CAAE;QAClDa,cAAc,CAAC+C,OAAOF,IAAI,CAAC,GAAGE,OAAOD,KAAK;IAC5C;IAEA,MAAMZ,yBAAiD,CAAC;IACxD,KAAK,MAAM,CAACiB,KAAKL,MAAM,IAAI1C,OAAOuC,OAAO,CAACnE,OAAO4C,eAAe,EAAG;QACjE,IAAI,CAAC+B,IAAIC,UAAU,CAAC,YAAY;QAChC,MAAMP,OAAOM,IAAIE,KAAK,CAAC,UAAUC,MAAM;QACvCtD,cAAc,CAAC6C,KAAK,GAAGC;QACvBZ,sBAAsB,CAACiB,IAAI,GAAGL;IAChC;IAEA,OAAO;QACLpC,kBAAkBlC,OAAOG,QAAQ,CAACQ,OAAO,IAAI,EAAE;QAC/CwB,mBACEnC,OAAOG,QAAQ,CAACgC,iBAAiB,IAAI4C,iCAAiC/E,OAAO4C,eAAe;QAC9FpB;QACAkC;QACAD,sBAAsB,CAAC;IACzB;AACF;AAEA,SAASiB,mBAAmB1E,MAI3B;IAOC,MAAMkC,mBAAmD,EAAE;IAC3D,MAAMC,oBAA8B,EAAE;IACtC,MAAMX,iBAAyC,CAAC;IAChD,MAAMkC,yBAAiD,CAAC;IACxD,MAAMD,uBAA+C,CAAC;IAEtD,KAAK,MAAMc,UAAUvE,OAAOuD,cAAc,CAAE;QAC1C,IAAIgB,OAAOS,IAAI,KAAK,MAAM;YACxB,MAAML,MAAM,CAAC,OAAO,EAAEJ,OAAOF,IAAI,EAAE;YACnC,MAAMC,QAAQtE,OAAO4C,eAAe,CAAC+B,IAAI;YACzC,IAAIL,UAAU9D,WAAW;gBACvB,MAAM,IAAIb,0CAA0CK,OAAOK,UAAU,EAAEkE,OAAOF,IAAI;YACpF;YACA7C,cAAc,CAAC+C,OAAOF,IAAI,CAAC,GAAGC;YAC9BnC,kBAAkBsC,IAAI,CAACF,OAAOF,IAAI;YAClCX,sBAAsB,CAACiB,IAAI,GAAGL;YAC9Bb,oBAAoB,CAACkB,IAAI,GAAGL;YAC5B;QACF;QAEA,MAAMA,QAAQC,OAAOD,KAAK;QAC1B,IAAIA,UAAU9D,WAAW;YACvB,MAAM,IAAIb,0CAA0CK,OAAOK,UAAU,EAAEkE,OAAOF,IAAI;QACpF;QACA7C,cAAc,CAAC+C,OAAOF,IAAI,CAAC,GAAGC;QAC9B,IAAIC,OAAOC,MAAM,EAAE;YACjB,MAAMG,MAAM,CAAC,OAAO,EAAEJ,OAAOF,IAAI,EAAE;YACnClC,kBAAkBsC,IAAI,CAACF,OAAOF,IAAI;YAClCX,sBAAsB,CAACiB,IAAI,GAAGL;YAC9Bb,oBAAoB,CAACkB,IAAI,GAAGL;QAC9B,OAAO;YACLpC,iBAAiBuC,IAAI,CAAC;gBAACJ,MAAME,OAAOF,IAAI;gBAAEC;YAAK;QACjD;IACF;IAEA,OAAO;QACLpC;QACAC;QACAX;QACAkC;QACAD;IACF;AACF;AAEA,SAAST,wBACP3C,UAA4B,EAC5B4E,oBAAmC,EACnCjE,MAA6B,EAC7BV,IAAsC;IAEtC,IAAI,CAAE,CAAA,mBAAmBA,IAAG,GAAI;QAC9B,IAAI2E,yBAAyB,MAAM,OAAO;QAC1C,OAAOjE,OAAOkE,IAAI,CAAC,CAAC5D,QAAUA,MAAM6D,EAAE,KAAKF,wBAAwBA,uBAAuB;IAC5F;IAEA,MAAMG,wBAAwB9E,KAAKW,aAAa,IAAI;IACpD,IAAImE,0BAA0B,MAAM,OAAO;IAC3C,IAAIpE,OAAOkE,IAAI,CAAC,CAAC5D,QAAUA,MAAM6D,EAAE,KAAKC,wBAAwB,OAAOA;IACvE,MAAM,IAAI1F,uBAAuB,MAAMW,YAAY+E;AACrD;AAEA,SAASrE,iBACPC,MAA6B,EAC7BqB,YAA2B;IAE3B,MAAMf,QAAQN,OAAOqE,IAAI,CAAC,CAACC,YAAcA,UAAUH,EAAE,KAAK9C,iBAAiBrB,MAAM,CAAC,EAAE;IACpF,IAAI,CAACM,OAAO,MAAM,IAAIiE,MAAM;IAC5B,OAAOjE;AACT;AAEA,eAAeuB,yBAAyB7C,MAGvC;IACC,MAAM6B,SAAS,MAAMlD,sBAAsB;QACzCyB,aAAaJ,OAAOI,WAAW;QAC/BoC,WAAWtD,qBAAqBc,OAAOK,UAAU;IACnD;IACA,OAAOhB,sCAAsCwC;AAC/C;AAEA,SAASuB,+CAA+CpD,MAKvD;IACC,IACEA,OAAOM,IAAI,CAACY,QAAQ,KAAKV,aACzBgF,cAAcxF,OAAOM,IAAI,CAACY,QAAQ,MAAMsE,cAAcxF,OAAOG,QAAQ,CAACkB,OAAO,IAAI,KACjF;QACA;IACF;IAEA,MAAMoE,qBACJzF,OAAO4C,eAAe,CAAC/B,OAAO,KAAKL,aAAaR,OAAOM,IAAI,CAACO,OAAO,KAAKL;IAC1E,MAAMkF,2BACJ1F,OAAOM,IAAI,CAACK,OAAO,KAAKH,YACpBoB,OAAO+D,IAAI,CAAC3F,OAAO4C,eAAe,EAAEsC,IAAI,CAAC,CAACP,MAAQA,IAAIC,UAAU,CAAC,cACjE5E,OAAOM,IAAI,CAACK,OAAO,CAACuE,IAAI,CAAC,CAACX,SAAWA,OAAOS,IAAI,KAAK;IAE3D,IAAIS,sBAAsBC,0BAA0B;QAClD,MAAM,IAAIjG,kDAAkDO,OAAOK,UAAU;IAC/E;AACF;AAEA,SAASmF,cAAclB,KAAa;IAClC,OAAOhF,+BAA+BgF;AACxC;AAEA,eAAeV,mCAAmC5D,MAKjD;IACC,MAAM4F,gBAAgBhE,OAAO+D,IAAI,CAAC3F,OAAO6D,gBAAgB;IACzD,IAAI+B,cAAcd,MAAM,KAAK,GAAG;IAEhC,MAAMe,mBAAmBjE,OAAOkE,WAAW,CACzCF,cAAcG,OAAO,CAAC,CAACpB;QACrB,MAAML,QAAQtE,OAAO4C,eAAe,CAAC+B,IAAI;QACzC,OAAOL,UAAU9D,YAAY,EAAE,GAAG;YAAC;gBAACmE;gBAAKL;aAAM;SAAC;IAClD;IAEF,IAAI1C,OAAO+D,IAAI,CAACE,kBAAkBf,MAAM,GAAG,GAAG;QAC5C,MAAMlG,WAAW;YACfwB,aAAaJ,OAAOI,WAAW;YAC/BoC,WAAWxC,OAAOwC,SAAS;YAC3BX,QAAQ1C,+BAA+B0G;QACzC;IACF;IAEA,MAAMG,UAAUJ,cAAcK,MAAM,CAAC,CAACtB,MAAQ3E,OAAO4C,eAAe,CAAC+B,IAAI,KAAKnE;IAC9E,IAAIwF,QAAQlB,MAAM,KAAK,GAAG;IAE1B,MAAMpG,cAAc;QAClB0B,aAAaJ,OAAOI,WAAW;QAC/BoC,WAAWxC,OAAOwC,SAAS;QAC3BmD,MAAMK,QAAQ5B,GAAG,CAAChF;IACpB;AACF;AAEA,eAAe2E,8BAA8B/D,MAM5C;IACC,IAAI,CAACA,OAAOiE,cAAc,EAAE;IAE5B,MAAMiC,WAAW,IAAIC,IAAIvE,OAAO+D,IAAI,CAAC3F,OAAOgE,UAAU;IACtD,MAAMoC,YAAYxE,OAAO+D,IAAI,CAAC3F,OAAO4C,eAAe,EAAEqD,MAAM,CAC1D,CAACtB,MAAQA,IAAIC,UAAU,CAAC,cAAc,CAACsB,SAASG,GAAG,CAAC1B;IAEtD,IAAIyB,UAAUtB,MAAM,KAAK,GAAG;IAE5B,MAAMpG,cAAc;QAClB0B,aAAaJ,OAAOI,WAAW;QAC/BoC,WAAWtD,qBAAqBc,OAAOK,UAAU;QACjDsF,MAAMS,UAAUhC,GAAG,CAAChF;IACtB;AACF;AAEA,SAAS2F,iCAAiCuB,WAAmC;IAC3E,OAAO1E,OAAO+D,IAAI,CAACW,aAChBL,MAAM,CAAC,CAACtB,MAAQA,IAAIC,UAAU,CAAC,YAC/BR,GAAG,CAAC,CAACO,MAAQA,IAAIE,KAAK,CAAC,UAAUC,MAAM,GACvCyB,IAAI;AACT"}
@@ -0,0 +1,4 @@
1
+ export declare function appendCustomProviderPath(baseUrl: string, segment: string): URL;
2
+ export declare function normalizeCustomProviderBaseUrl(baseUrl: string): string;
3
+ export declare function googleDiscoveryUrl(baseUrl: string): string;
4
+ //# sourceMappingURL=custom-provider-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-provider-url.d.ts","sourceRoot":"","sources":["../../src/core/custom-provider-url.ts"],"names":[],"mappings":"AAGA,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,CAK9E;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQtE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D"}
@@ -0,0 +1,23 @@
1
+ const TRAILING_SLASHES_PATTERN = /\/+$/;
2
+ const GOOGLE_DISCOVERY_VERSION_PATTERN = /\/v1beta$/;
3
+ export function appendCustomProviderPath(baseUrl, segment) {
4
+ const url = new URL(normalizeCustomProviderBaseUrl(baseUrl));
5
+ const path = url.pathname.replace(TRAILING_SLASHES_PATTERN, '');
6
+ url.pathname = `${path}/${segment}`;
7
+ return url;
8
+ }
9
+ export function normalizeCustomProviderBaseUrl(baseUrl) {
10
+ try {
11
+ const url = new URL(baseUrl);
12
+ url.pathname = url.pathname.replace(TRAILING_SLASHES_PATTERN, '');
13
+ return url.href;
14
+ } catch {
15
+ return baseUrl.replace(TRAILING_SLASHES_PATTERN, '');
16
+ }
17
+ }
18
+ export function googleDiscoveryUrl(baseUrl) {
19
+ const normalizedBaseUrl = baseUrl.replace(GOOGLE_DISCOVERY_VERSION_PATTERN, '');
20
+ return appendCustomProviderPath(normalizedBaseUrl, 'v1beta/models').toString();
21
+ }
22
+
23
+ //# sourceMappingURL=custom-provider-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/custom-provider-url.ts"],"sourcesContent":["const TRAILING_SLASHES_PATTERN = /\\/+$/;\nconst GOOGLE_DISCOVERY_VERSION_PATTERN = /\\/v1beta$/;\n\nexport function appendCustomProviderPath(baseUrl: string, segment: string): URL {\n const url = new URL(normalizeCustomProviderBaseUrl(baseUrl));\n const path = url.pathname.replace(TRAILING_SLASHES_PATTERN, '');\n url.pathname = `${path}/${segment}`;\n return url;\n}\n\nexport function normalizeCustomProviderBaseUrl(baseUrl: string): string {\n try {\n const url = new URL(baseUrl);\n url.pathname = url.pathname.replace(TRAILING_SLASHES_PATTERN, '');\n return url.href;\n } catch {\n return baseUrl.replace(TRAILING_SLASHES_PATTERN, '');\n }\n}\n\nexport function googleDiscoveryUrl(baseUrl: string): string {\n const normalizedBaseUrl = baseUrl.replace(GOOGLE_DISCOVERY_VERSION_PATTERN, '');\n return appendCustomProviderPath(normalizedBaseUrl, 'v1beta/models').toString();\n}\n"],"names":["TRAILING_SLASHES_PATTERN","GOOGLE_DISCOVERY_VERSION_PATTERN","appendCustomProviderPath","baseUrl","segment","url","URL","normalizeCustomProviderBaseUrl","path","pathname","replace","href","googleDiscoveryUrl","normalizedBaseUrl","toString"],"mappings":"AAAA,MAAMA,2BAA2B;AACjC,MAAMC,mCAAmC;AAEzC,OAAO,SAASC,yBAAyBC,OAAe,EAAEC,OAAe;IACvE,MAAMC,MAAM,IAAIC,IAAIC,+BAA+BJ;IACnD,MAAMK,OAAOH,IAAII,QAAQ,CAACC,OAAO,CAACV,0BAA0B;IAC5DK,IAAII,QAAQ,GAAG,GAAGD,KAAK,CAAC,EAAEJ,SAAS;IACnC,OAAOC;AACT;AAEA,OAAO,SAASE,+BAA+BJ,OAAe;IAC5D,IAAI;QACF,MAAME,MAAM,IAAIC,IAAIH;QACpBE,IAAII,QAAQ,GAAGJ,IAAII,QAAQ,CAACC,OAAO,CAACV,0BAA0B;QAC9D,OAAOK,IAAIM,IAAI;IACjB,EAAE,OAAM;QACN,OAAOR,QAAQO,OAAO,CAACV,0BAA0B;IACnD;AACF;AAEA,OAAO,SAASY,mBAAmBT,OAAe;IAChD,MAAMU,oBAAoBV,QAAQO,OAAO,CAACT,kCAAkC;IAC5E,OAAOC,yBAAyBW,mBAAmB,iBAAiBC,QAAQ;AAC9E"}
@@ -0,0 +1,3 @@
1
+ import type { DiscoverCustomModelProviderModelsBodyDto, DiscoverCustomModelProviderModelsResponseDto } from '@shipfox/api-agent-dto';
2
+ export declare function discoverCustomModelProviderModels(params: DiscoverCustomModelProviderModelsBodyDto): Promise<DiscoverCustomModelProviderModelsResponseDto>;
3
+ //# sourceMappingURL=discover-custom-model-provider-models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover-custom-model-provider-models.d.ts","sourceRoot":"","sources":["../../src/core/discover-custom-model-provider-models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,wCAAwC,EACxC,4CAA4C,EAE7C,MAAM,wBAAwB,CAAC;AAehC,wBAAsB,iCAAiC,CACrD,MAAM,EAAE,wCAAwC,GAC/C,OAAO,CAAC,4CAA4C,CAAC,CAkBvD"}
@@ -0,0 +1,122 @@
1
+ import { MAX_MODEL_COUNT } from '@shipfox/api-agent-dto';
2
+ import { assertEgressAllowed } from '@shipfox/node-egress-guard';
3
+ import { config } from '#config.js';
4
+ import { appendCustomProviderPath, googleDiscoveryUrl } from './custom-provider-url.js';
5
+ import { egressPolicy } from './model-provider-validation.js';
6
+ const GOOGLE_MODEL_PREFIX_PATTERN = /^models\//;
7
+ const MAX_DISCOVERY_RESPONSE_BYTES = 512 * 1024;
8
+ export async function discoverCustomModelProviderModels(params) {
9
+ await assertEgressAllowed(params.base_url, egressPolicy());
10
+ try {
11
+ const response = await fetch(discoveryUrl(params.base_url, params.api), {
12
+ method: 'GET',
13
+ headers: discoveryHeaders(params.api, params.api_key, params.headers ?? []),
14
+ redirect: 'error',
15
+ signal: AbortSignal.timeout(config.AGENT_PROVIDER_VALIDATION_TIMEOUT_MS)
16
+ });
17
+ if (!response.ok) return {
18
+ models: []
19
+ };
20
+ const payload = await readBoundedJson(response);
21
+ if (payload === undefined) return {
22
+ models: []
23
+ };
24
+ return {
25
+ models: parseModelList(payload)
26
+ };
27
+ } catch {
28
+ return {
29
+ models: []
30
+ };
31
+ }
32
+ }
33
+ function discoveryUrl(baseUrl, api) {
34
+ if (api === 'google-generative-ai') return googleDiscoveryUrl(baseUrl);
35
+ return appendCustomProviderPath(baseUrl, 'models').toString();
36
+ }
37
+ function discoveryHeaders(api, apiKey, headers) {
38
+ const result = new Headers(headers.map((header)=>[
39
+ header.name,
40
+ header.value
41
+ ]));
42
+ if (!apiKey) return result;
43
+ switch(api){
44
+ case 'anthropic-messages':
45
+ result.set('x-api-key', apiKey);
46
+ result.set('anthropic-version', '2023-06-01');
47
+ break;
48
+ case 'google-generative-ai':
49
+ result.set('x-goog-api-key', apiKey);
50
+ break;
51
+ default:
52
+ result.set('authorization', `Bearer ${apiKey}`);
53
+ break;
54
+ }
55
+ return result;
56
+ }
57
+ async function readBoundedJson(response) {
58
+ if (!response.body) return undefined;
59
+ const reader = response.body.getReader();
60
+ const chunks = [];
61
+ let totalBytes = 0;
62
+ try {
63
+ while(true){
64
+ const { value, done } = await reader.read();
65
+ if (done) break;
66
+ if (value === undefined) continue;
67
+ totalBytes += value.byteLength;
68
+ if (totalBytes > MAX_DISCOVERY_RESPONSE_BYTES) {
69
+ await reader.cancel();
70
+ return undefined;
71
+ }
72
+ chunks.push(value);
73
+ }
74
+ } finally{
75
+ reader.releaseLock();
76
+ }
77
+ const buffer = new Uint8Array(totalBytes);
78
+ let offset = 0;
79
+ for (const chunk of chunks){
80
+ buffer.set(chunk, offset);
81
+ offset += chunk.byteLength;
82
+ }
83
+ return JSON.parse(new TextDecoder().decode(buffer));
84
+ }
85
+ function parseModelList(payload) {
86
+ const candidates = extractModelCandidates(payload);
87
+ const models = candidates.map(parseModelCandidate).filter(isDiscoveredModel);
88
+ const seen = new Set();
89
+ return models.filter((model)=>{
90
+ if (seen.has(model.id)) return false;
91
+ seen.add(model.id);
92
+ return true;
93
+ }).slice(0, MAX_MODEL_COUNT);
94
+ }
95
+ function extractModelCandidates(payload) {
96
+ if (Array.isArray(payload)) return payload;
97
+ if (!payload || typeof payload !== 'object') return [];
98
+ const record = payload;
99
+ if (Array.isArray(record.data)) return record.data;
100
+ if (Array.isArray(record.models)) return record.models;
101
+ return [];
102
+ }
103
+ function parseModelCandidate(candidate) {
104
+ if (!candidate || typeof candidate !== 'object') return undefined;
105
+ const record = candidate;
106
+ const rawId = firstString(record.id, record.name);
107
+ if (!rawId) return undefined;
108
+ const id = rawId.replace(GOOGLE_MODEL_PREFIX_PATTERN, '');
109
+ const label = firstString(record.label, record.display_name, record.displayName, record.name) ?? id;
110
+ return {
111
+ id,
112
+ label: label.replace(GOOGLE_MODEL_PREFIX_PATTERN, '')
113
+ };
114
+ }
115
+ function firstString(...values) {
116
+ return values.find((value)=>typeof value === 'string' && value.length > 0);
117
+ }
118
+ function isDiscoveredModel(model) {
119
+ return model !== undefined;
120
+ }
121
+
122
+ //# sourceMappingURL=discover-custom-model-provider-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/discover-custom-model-provider-models.ts"],"sourcesContent":["import type {\n CustomModelProviderHeaderDto,\n DiscoverCustomModelProviderModelsBodyDto,\n DiscoverCustomModelProviderModelsResponseDto,\n ModelProviderApi,\n} from '@shipfox/api-agent-dto';\nimport {MAX_MODEL_COUNT} from '@shipfox/api-agent-dto';\nimport {assertEgressAllowed} from '@shipfox/node-egress-guard';\nimport {config} from '#config.js';\nimport {appendCustomProviderPath, googleDiscoveryUrl} from './custom-provider-url.js';\nimport {egressPolicy} from './model-provider-validation.js';\n\nconst GOOGLE_MODEL_PREFIX_PATTERN = /^models\\//;\nconst MAX_DISCOVERY_RESPONSE_BYTES = 512 * 1024;\n\ninterface DiscoveredModel {\n id: string;\n label: string;\n}\n\nexport async function discoverCustomModelProviderModels(\n params: DiscoverCustomModelProviderModelsBodyDto,\n): Promise<DiscoverCustomModelProviderModelsResponseDto> {\n await assertEgressAllowed(params.base_url, egressPolicy());\n\n try {\n const response = await fetch(discoveryUrl(params.base_url, params.api), {\n method: 'GET',\n headers: discoveryHeaders(params.api, params.api_key, params.headers ?? []),\n redirect: 'error',\n signal: AbortSignal.timeout(config.AGENT_PROVIDER_VALIDATION_TIMEOUT_MS),\n });\n if (!response.ok) return {models: []};\n\n const payload = await readBoundedJson(response);\n if (payload === undefined) return {models: []};\n return {models: parseModelList(payload)};\n } catch {\n return {models: []};\n }\n}\n\nfunction discoveryUrl(baseUrl: string, api: ModelProviderApi): string {\n if (api === 'google-generative-ai') return googleDiscoveryUrl(baseUrl);\n return appendCustomProviderPath(baseUrl, 'models').toString();\n}\n\nfunction discoveryHeaders(\n api: ModelProviderApi,\n apiKey: string | undefined,\n headers: CustomModelProviderHeaderDto[],\n): Headers {\n const result = new Headers(headers.map((header) => [header.name, header.value]));\n if (!apiKey) return result;\n\n switch (api) {\n case 'anthropic-messages':\n result.set('x-api-key', apiKey);\n result.set('anthropic-version', '2023-06-01');\n break;\n case 'google-generative-ai':\n result.set('x-goog-api-key', apiKey);\n break;\n default:\n result.set('authorization', `Bearer ${apiKey}`);\n break;\n }\n\n return result;\n}\n\nasync function readBoundedJson(response: Response): Promise<unknown | undefined> {\n if (!response.body) return undefined;\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n\n try {\n while (true) {\n const {value, done} = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n\n totalBytes += value.byteLength;\n if (totalBytes > MAX_DISCOVERY_RESPONSE_BYTES) {\n await reader.cancel();\n return undefined;\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n const buffer = new Uint8Array(totalBytes);\n let offset = 0;\n for (const chunk of chunks) {\n buffer.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return JSON.parse(new TextDecoder().decode(buffer));\n}\n\nfunction parseModelList(payload: unknown): DiscoveredModel[] {\n const candidates = extractModelCandidates(payload);\n const models = candidates.map(parseModelCandidate).filter(isDiscoveredModel);\n const seen = new Set<string>();\n return models\n .filter((model) => {\n if (seen.has(model.id)) return false;\n seen.add(model.id);\n return true;\n })\n .slice(0, MAX_MODEL_COUNT);\n}\n\nfunction extractModelCandidates(payload: unknown): unknown[] {\n if (Array.isArray(payload)) return payload;\n if (!payload || typeof payload !== 'object') return [];\n\n const record = payload as Record<string, unknown>;\n if (Array.isArray(record.data)) return record.data;\n if (Array.isArray(record.models)) return record.models;\n return [];\n}\n\nfunction parseModelCandidate(candidate: unknown): DiscoveredModel | undefined {\n if (!candidate || typeof candidate !== 'object') return undefined;\n const record = candidate as Record<string, unknown>;\n const rawId = firstString(record.id, record.name);\n if (!rawId) return undefined;\n\n const id = rawId.replace(GOOGLE_MODEL_PREFIX_PATTERN, '');\n const label =\n firstString(record.label, record.display_name, record.displayName, record.name) ?? id;\n return {id, label: label.replace(GOOGLE_MODEL_PREFIX_PATTERN, '')};\n}\n\nfunction firstString(...values: unknown[]): string | undefined {\n return values.find((value): value is string => typeof value === 'string' && value.length > 0);\n}\n\nfunction isDiscoveredModel(model: DiscoveredModel | undefined): model is DiscoveredModel {\n return model !== undefined;\n}\n"],"names":["MAX_MODEL_COUNT","assertEgressAllowed","config","appendCustomProviderPath","googleDiscoveryUrl","egressPolicy","GOOGLE_MODEL_PREFIX_PATTERN","MAX_DISCOVERY_RESPONSE_BYTES","discoverCustomModelProviderModels","params","base_url","response","fetch","discoveryUrl","api","method","headers","discoveryHeaders","api_key","redirect","signal","AbortSignal","timeout","AGENT_PROVIDER_VALIDATION_TIMEOUT_MS","ok","models","payload","readBoundedJson","undefined","parseModelList","baseUrl","toString","apiKey","result","Headers","map","header","name","value","set","body","reader","getReader","chunks","totalBytes","done","read","byteLength","cancel","push","releaseLock","buffer","Uint8Array","offset","chunk","JSON","parse","TextDecoder","decode","candidates","extractModelCandidates","parseModelCandidate","filter","isDiscoveredModel","seen","Set","model","has","id","add","slice","Array","isArray","record","data","candidate","rawId","firstString","replace","label","display_name","displayName","values","find","length"],"mappings":"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"}