@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.
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +105 -0
- package/LICENSE +21 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +60 -0
- package/dist/config.js.map +1 -0
- package/dist/core/credential-fingerprints.d.ts +10 -0
- package/dist/core/credential-fingerprints.d.ts.map +1 -0
- package/dist/core/credential-fingerprints.js +65 -0
- package/dist/core/credential-fingerprints.js.map +1 -0
- package/dist/core/custom-model-provider-config-service.d.ts +26 -0
- package/dist/core/custom-model-provider-config-service.d.ts.map +1 -0
- package/dist/core/custom-model-provider-config-service.js +390 -0
- package/dist/core/custom-model-provider-config-service.js.map +1 -0
- package/dist/core/custom-provider-url.d.ts +4 -0
- package/dist/core/custom-provider-url.d.ts.map +1 -0
- package/dist/core/custom-provider-url.js +23 -0
- package/dist/core/custom-provider-url.js.map +1 -0
- package/dist/core/discover-custom-model-provider-models.d.ts +3 -0
- package/dist/core/discover-custom-model-provider-models.d.ts.map +1 -0
- package/dist/core/discover-custom-model-provider-models.js +122 -0
- package/dist/core/discover-custom-model-provider-models.js.map +1 -0
- package/dist/core/entities/agent-workspace-settings.d.ts +9 -0
- package/dist/core/entities/agent-workspace-settings.d.ts.map +1 -0
- package/dist/core/entities/agent-workspace-settings.js +3 -0
- package/dist/core/entities/agent-workspace-settings.js.map +1 -0
- package/dist/core/entities/model-provider-config.d.ts +19 -0
- package/dist/core/entities/model-provider-config.d.ts.map +1 -0
- package/dist/core/entities/model-provider-config.js +3 -0
- package/dist/core/entities/model-provider-config.js.map +1 -0
- package/dist/core/errors.d.ts +62 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +80 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/harness/claude.d.ts +6 -0
- package/dist/core/harness/claude.d.ts.map +1 -0
- package/dist/core/harness/claude.js +104 -0
- package/dist/core/harness/claude.js.map +1 -0
- package/dist/core/harness/index.d.ts +4 -0
- package/dist/core/harness/index.d.ts.map +1 -0
- package/dist/core/harness/index.js +5 -0
- package/dist/core/harness/index.js.map +1 -0
- package/dist/core/harness/pi.d.ts +6 -0
- package/dist/core/harness/pi.d.ts.map +1 -0
- package/dist/core/harness/pi.js +16 -0
- package/dist/core/harness/pi.js.map +1 -0
- package/dist/core/harness/registry.d.ts +22 -0
- package/dist/core/harness/registry.d.ts.map +1 -0
- package/dist/core/harness/registry.js +46 -0
- package/dist/core/harness/registry.js.map +1 -0
- package/dist/core/index.d.ts +12 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +11 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/model-provider-catalog.d.ts +3 -0
- package/dist/core/model-provider-catalog.d.ts.map +1 -0
- package/dist/core/model-provider-catalog.js +25 -0
- package/dist/core/model-provider-catalog.js.map +1 -0
- package/dist/core/model-provider-config-service.d.ts +32 -0
- package/dist/core/model-provider-config-service.d.ts.map +1 -0
- package/dist/core/model-provider-config-service.js +133 -0
- package/dist/core/model-provider-config-service.js.map +1 -0
- package/dist/core/model-provider-validation.d.ts +31 -0
- package/dist/core/model-provider-validation.d.ts.map +1 -0
- package/dist/core/model-provider-validation.js +220 -0
- package/dist/core/model-provider-validation.js.map +1 -0
- package/dist/core/resolve-agent-config.d.ts +32 -0
- package/dist/core/resolve-agent-config.d.ts.map +1 -0
- package/dist/core/resolve-agent-config.js +143 -0
- package/dist/core/resolve-agent-config.js.map +1 -0
- package/dist/core/resolve-runtime-credentials.d.ts +21 -0
- package/dist/core/resolve-runtime-credentials.d.ts.map +1 -0
- package/dist/core/resolve-runtime-credentials.js +101 -0
- package/dist/core/resolve-runtime-credentials.js.map +1 -0
- package/dist/core/workspace-agent-defaults-resolver.d.ts +3 -0
- package/dist/core/workspace-agent-defaults-resolver.d.ts.map +1 -0
- package/dist/core/workspace-agent-defaults-resolver.js +26 -0
- package/dist/core/workspace-agent-defaults-resolver.js.map +1 -0
- package/dist/db/agent-defaults-snapshot.d.ts +9 -0
- package/dist/db/agent-defaults-snapshot.d.ts.map +1 -0
- package/dist/db/agent-defaults-snapshot.js +24 -0
- package/dist/db/agent-defaults-snapshot.js.map +1 -0
- package/dist/db/agent-workspace-settings.d.ts +12 -0
- package/dist/db/agent-workspace-settings.d.ts.map +1 -0
- package/dist/db/agent-workspace-settings.js +53 -0
- package/dist/db/agent-workspace-settings.js.map +1 -0
- package/dist/db/db.d.ts +771 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +20 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/index.d.ts +9 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +11 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/model-provider-configs.d.ts +38 -0
- package/dist/db/model-provider-configs.d.ts.map +1 -0
- package/dist/db/model-provider-configs.js +159 -0
- package/dist/db/model-provider-configs.js.map +1 -0
- package/dist/db/schema/agent-workspace-settings.d.ts +97 -0
- package/dist/db/schema/agent-workspace-settings.d.ts.map +1 -0
- package/dist/db/schema/agent-workspace-settings.js +26 -0
- package/dist/db/schema/agent-workspace-settings.js.map +1 -0
- package/dist/db/schema/common.d.ts +2 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +4 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/model-provider-configs.d.ts +296 -0
- package/dist/db/schema/model-provider-configs.d.ts.map +1 -0
- package/dist/db/schema/model-provider-configs.js +52 -0
- package/dist/db/schema/model-provider-configs.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +10 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +10 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/dto/index.d.ts +2 -0
- package/dist/presentation/dto/index.d.ts.map +1 -0
- package/dist/presentation/dto/index.js +3 -0
- package/dist/presentation/dto/index.js.map +1 -0
- package/dist/presentation/dto/model-provider-config.d.ts +6 -0
- package/dist/presentation/dto/model-provider-config.d.ts.map +1 -0
- package/dist/presentation/dto/model-provider-config.js +32 -0
- package/dist/presentation/dto/model-provider-config.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-model-provider.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-model-provider.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-model-provider.js +66 -0
- package/dist/presentation/e2eRoutes/create-model-provider.js.map +1 -0
- package/dist/presentation/e2eRoutes/index.d.ts +3 -0
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/index.js +9 -0
- package/dist/presentation/e2eRoutes/index.js.map +1 -0
- package/dist/presentation/index.d.ts +2 -0
- package/dist/presentation/index.d.ts.map +1 -0
- package/dist/presentation/index.js +3 -0
- package/dist/presentation/index.js.map +1 -0
- package/dist/presentation/routes/create-custom-model-provider.d.ts +2 -0
- package/dist/presentation/routes/create-custom-model-provider.d.ts.map +1 -0
- package/dist/presentation/routes/create-custom-model-provider.js +45 -0
- package/dist/presentation/routes/create-custom-model-provider.js.map +1 -0
- package/dist/presentation/routes/delete-model-provider-config.d.ts +2 -0
- package/dist/presentation/routes/delete-model-provider-config.d.ts.map +1 -0
- package/dist/presentation/routes/delete-model-provider-config.js +38 -0
- package/dist/presentation/routes/delete-model-provider-config.js.map +1 -0
- package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.d.ts +2 -0
- package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.d.ts.map +1 -0
- package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.js +37 -0
- package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.js.map +1 -0
- package/dist/presentation/routes/discover-custom-model-provider-models.d.ts +2 -0
- package/dist/presentation/routes/discover-custom-model-provider-models.d.ts.map +1 -0
- package/dist/presentation/routes/discover-custom-model-provider-models.js +31 -0
- package/dist/presentation/routes/discover-custom-model-provider-models.js.map +1 -0
- package/dist/presentation/routes/errors.d.ts +2 -0
- package/dist/presentation/routes/errors.d.ts.map +1 -0
- package/dist/presentation/routes/errors.js +112 -0
- package/dist/presentation/routes/errors.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +3 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +39 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/list-model-provider-catalog.d.ts +2 -0
- package/dist/presentation/routes/list-model-provider-catalog.d.ts.map +1 -0
- package/dist/presentation/routes/list-model-provider-catalog.js +18 -0
- package/dist/presentation/routes/list-model-provider-catalog.js.map +1 -0
- package/dist/presentation/routes/list-model-provider-configs.d.ts +2 -0
- package/dist/presentation/routes/list-model-provider-configs.d.ts.map +1 -0
- package/dist/presentation/routes/list-model-provider-configs.js +37 -0
- package/dist/presentation/routes/list-model-provider-configs.js.map +1 -0
- package/dist/presentation/routes/set-default-harness.d.ts +2 -0
- package/dist/presentation/routes/set-default-harness.d.ts.map +1 -0
- package/dist/presentation/routes/set-default-harness.js +38 -0
- package/dist/presentation/routes/set-default-harness.js.map +1 -0
- package/dist/presentation/routes/set-default-model-provider.d.ts +2 -0
- package/dist/presentation/routes/set-default-model-provider.d.ts.map +1 -0
- package/dist/presentation/routes/set-default-model-provider.js +48 -0
- package/dist/presentation/routes/set-default-model-provider.js.map +1 -0
- package/dist/presentation/routes/update-custom-model-provider.d.ts +2 -0
- package/dist/presentation/routes/update-custom-model-provider.d.ts.map +1 -0
- package/dist/presentation/routes/update-custom-model-provider.js +47 -0
- package/dist/presentation/routes/update-custom-model-provider.js.map +1 -0
- package/dist/presentation/routes/update-model-provider-default-model.d.ts +2 -0
- package/dist/presentation/routes/update-model-provider-default-model.d.ts.map +1 -0
- package/dist/presentation/routes/update-model-provider-default-model.js +38 -0
- package/dist/presentation/routes/update-model-provider-default-model.js.map +1 -0
- package/dist/presentation/routes/upsert-model-provider-config.d.ts +2 -0
- package/dist/presentation/routes/upsert-model-provider-config.d.ts.map +1 -0
- package/dist/presentation/routes/upsert-model-provider-config.js +52 -0
- package/dist/presentation/routes/upsert-model-provider-config.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_daffy_leopardon.sql +28 -0
- package/drizzle/0001_default_harness.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +199 -0
- package/drizzle/meta/0001_snapshot.json +205 -0
- package/drizzle/meta/_journal.json +20 -0
- package/drizzle.config.ts +7 -0
- package/package.json +109 -0
- package/src/config.test.ts +118 -0
- package/src/config.ts +75 -0
- package/src/core/catalog-registry.test.ts +48 -0
- package/src/core/credential-fingerprints.test.ts +34 -0
- package/src/core/credential-fingerprints.ts +84 -0
- package/src/core/custom-model-provider-config-service.ts +537 -0
- package/src/core/custom-provider-url.ts +24 -0
- package/src/core/discover-custom-model-provider-models.ts +147 -0
- package/src/core/entities/agent-workspace-settings.ts +9 -0
- package/src/core/entities/model-provider-config.ts +25 -0
- package/src/core/errors.ts +139 -0
- package/src/core/harness/claude.ts +75 -0
- package/src/core/harness/index.ts +19 -0
- package/src/core/harness/pi.ts +18 -0
- package/src/core/harness/registry.test.ts +281 -0
- package/src/core/harness/registry.ts +108 -0
- package/src/core/index.ts +56 -0
- package/src/core/model-provider-catalog.ts +33 -0
- package/src/core/model-provider-config-service.test.ts +940 -0
- package/src/core/model-provider-config-service.ts +207 -0
- package/src/core/model-provider-validation.test.ts +354 -0
- package/src/core/model-provider-validation.ts +276 -0
- package/src/core/resolve-agent-config.test.ts +441 -0
- package/src/core/resolve-agent-config.ts +272 -0
- package/src/core/resolve-runtime-credentials.test.ts +316 -0
- package/src/core/resolve-runtime-credentials.ts +147 -0
- package/src/core/workspace-agent-defaults-resolver.ts +42 -0
- package/src/db/agent-defaults-snapshot.ts +49 -0
- package/src/db/agent-workspace-settings.test.ts +111 -0
- package/src/db/agent-workspace-settings.ts +92 -0
- package/src/db/db.ts +20 -0
- package/src/db/index.ts +29 -0
- package/src/db/model-provider-configs.test.ts +281 -0
- package/src/db/model-provider-configs.ts +230 -0
- package/src/db/schema/agent-workspace-settings.ts +27 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/model-provider-configs.ts +80 -0
- package/src/index.ts +43 -0
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.ts +18 -0
- package/src/presentation/dto/index.ts +5 -0
- package/src/presentation/dto/model-provider-config.ts +41 -0
- package/src/presentation/e2eRoutes/create-model-provider.test.ts +47 -0
- package/src/presentation/e2eRoutes/create-model-provider.ts +61 -0
- package/src/presentation/e2eRoutes/index.ts +7 -0
- package/src/presentation/index.ts +1 -0
- package/src/presentation/routes/create-custom-model-provider.ts +45 -0
- package/src/presentation/routes/delete-model-provider-config.ts +31 -0
- package/src/presentation/routes/discover-custom-model-provider-models-by-slug.ts +42 -0
- package/src/presentation/routes/discover-custom-model-provider-models.ts +29 -0
- package/src/presentation/routes/errors.ts +122 -0
- package/src/presentation/routes/index.ts +37 -0
- package/src/presentation/routes/list-model-provider-catalog.ts +15 -0
- package/src/presentation/routes/list-model-provider-configs.ts +33 -0
- package/src/presentation/routes/model-provider-catalog.test.ts +73 -0
- package/src/presentation/routes/model-provider-config.test.ts +807 -0
- package/src/presentation/routes/set-default-harness.ts +33 -0
- package/src/presentation/routes/set-default-model-provider.ts +47 -0
- package/src/presentation/routes/update-custom-model-provider.ts +50 -0
- package/src/presentation/routes/update-model-provider-default-model.ts +40 -0
- package/src/presentation/routes/upsert-model-provider-config.ts +53 -0
- package/test/env.ts +8 -0
- package/test/globalSetup.ts +25 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- 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":"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"}
|
|
@@ -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":"AAMA,SAAQA,eAAe,QAAO,yBAAyB;AACvD,SAAQC,mBAAmB,QAAO,6BAA6B;AAC/D,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,wBAAwB,EAAEC,kBAAkB,QAAO,2BAA2B;AACtF,SAAQC,YAAY,QAAO,iCAAiC;AAE5D,MAAMC,8BAA8B;AACpC,MAAMC,+BAA+B,MAAM;AAO3C,OAAO,eAAeC,kCACpBC,MAAgD;IAEhD,MAAMR,oBAAoBQ,OAAOC,QAAQ,EAAEL;IAE3C,IAAI;QACF,MAAMM,WAAW,MAAMC,MAAMC,aAAaJ,OAAOC,QAAQ,EAAED,OAAOK,GAAG,GAAG;YACtEC,QAAQ;YACRC,SAASC,iBAAiBR,OAAOK,GAAG,EAAEL,OAAOS,OAAO,EAAET,OAAOO,OAAO,IAAI,EAAE;YAC1EG,UAAU;YACVC,QAAQC,YAAYC,OAAO,CAACpB,OAAOqB,oCAAoC;QACzE;QACA,IAAI,CAACZ,SAASa,EAAE,EAAE,OAAO;YAACC,QAAQ,EAAE;QAAA;QAEpC,MAAMC,UAAU,MAAMC,gBAAgBhB;QACtC,IAAIe,YAAYE,WAAW,OAAO;YAACH,QAAQ,EAAE;QAAA;QAC7C,OAAO;YAACA,QAAQI,eAAeH;QAAQ;IACzC,EAAE,OAAM;QACN,OAAO;YAACD,QAAQ,EAAE;QAAA;IACpB;AACF;AAEA,SAASZ,aAAaiB,OAAe,EAAEhB,GAAqB;IAC1D,IAAIA,QAAQ,wBAAwB,OAAOV,mBAAmB0B;IAC9D,OAAO3B,yBAAyB2B,SAAS,UAAUC,QAAQ;AAC7D;AAEA,SAASd,iBACPH,GAAqB,EACrBkB,MAA0B,EAC1BhB,OAAuC;IAEvC,MAAMiB,SAAS,IAAIC,QAAQlB,QAAQmB,GAAG,CAAC,CAACC,SAAW;YAACA,OAAOC,IAAI;YAAED,OAAOE,KAAK;SAAC;IAC9E,IAAI,CAACN,QAAQ,OAAOC;IAEpB,OAAQnB;QACN,KAAK;YACHmB,OAAOM,GAAG,CAAC,aAAaP;YACxBC,OAAOM,GAAG,CAAC,qBAAqB;YAChC;QACF,KAAK;YACHN,OAAOM,GAAG,CAAC,kBAAkBP;YAC7B;QACF;YACEC,OAAOM,GAAG,CAAC,iBAAiB,CAAC,OAAO,EAAEP,QAAQ;YAC9C;IACJ;IAEA,OAAOC;AACT;AAEA,eAAeN,gBAAgBhB,QAAkB;IAC/C,IAAI,CAACA,SAAS6B,IAAI,EAAE,OAAOZ;IAE3B,MAAMa,SAAS9B,SAAS6B,IAAI,CAACE,SAAS;IACtC,MAAMC,SAAuB,EAAE;IAC/B,IAAIC,aAAa;IAEjB,IAAI;QACF,MAAO,KAAM;YACX,MAAM,EAACN,KAAK,EAAEO,IAAI,EAAC,GAAG,MAAMJ,OAAOK,IAAI;YACvC,IAAID,MAAM;YACV,IAAIP,UAAUV,WAAW;YAEzBgB,cAAcN,MAAMS,UAAU;YAC9B,IAAIH,aAAarC,8BAA8B;gBAC7C,MAAMkC,OAAOO,MAAM;gBACnB,OAAOpB;YACT;YACAe,OAAOM,IAAI,CAACX;QACd;IACF,SAAU;QACRG,OAAOS,WAAW;IACpB;IAEA,MAAMC,SAAS,IAAIC,WAAWR;IAC9B,IAAIS,SAAS;IACb,KAAK,MAAMC,SAASX,OAAQ;QAC1BQ,OAAOZ,GAAG,CAACe,OAAOD;QAClBA,UAAUC,MAAMP,UAAU;IAC5B;IAEA,OAAOQ,KAAKC,KAAK,CAAC,IAAIC,cAAcC,MAAM,CAACP;AAC7C;AAEA,SAAStB,eAAeH,OAAgB;IACtC,MAAMiC,aAAaC,uBAAuBlC;IAC1C,MAAMD,SAASkC,WAAWxB,GAAG,CAAC0B,qBAAqBC,MAAM,CAACC;IAC1D,MAAMC,OAAO,IAAIC;IACjB,OAAOxC,OACJqC,MAAM,CAAC,CAACI;QACP,IAAIF,KAAKG,GAAG,CAACD,MAAME,EAAE,GAAG,OAAO;QAC/BJ,KAAKK,GAAG,CAACH,MAAME,EAAE;QACjB,OAAO;IACT,GACCE,KAAK,CAAC,GAAGtE;AACd;AAEA,SAAS4D,uBAAuBlC,OAAgB;IAC9C,IAAI6C,MAAMC,OAAO,CAAC9C,UAAU,OAAOA;IACnC,IAAI,CAACA,WAAW,OAAOA,YAAY,UAAU,OAAO,EAAE;IAEtD,MAAM+C,SAAS/C;IACf,IAAI6C,MAAMC,OAAO,CAACC,OAAOC,IAAI,GAAG,OAAOD,OAAOC,IAAI;IAClD,IAAIH,MAAMC,OAAO,CAACC,OAAOhD,MAAM,GAAG,OAAOgD,OAAOhD,MAAM;IACtD,OAAO,EAAE;AACX;AAEA,SAASoC,oBAAoBc,SAAkB;IAC7C,IAAI,CAACA,aAAa,OAAOA,cAAc,UAAU,OAAO/C;IACxD,MAAM6C,SAASE;IACf,MAAMC,QAAQC,YAAYJ,OAAOL,EAAE,EAAEK,OAAOpC,IAAI;IAChD,IAAI,CAACuC,OAAO,OAAOhD;IAEnB,MAAMwC,KAAKQ,MAAME,OAAO,CAACxE,6BAA6B;IACtD,MAAMyE,QACJF,YAAYJ,OAAOM,KAAK,EAAEN,OAAOO,YAAY,EAAEP,OAAOQ,WAAW,EAAER,OAAOpC,IAAI,KAAK+B;IACrF,OAAO;QAACA;QAAIW,OAAOA,MAAMD,OAAO,CAACxE,6BAA6B;IAAG;AACnE;AAEA,SAASuE,YAAY,GAAGK,MAAiB;IACvC,OAAOA,OAAOC,IAAI,CAAC,CAAC7C,QAA2B,OAAOA,UAAU,YAAYA,MAAM8C,MAAM,GAAG;AAC7F;AAEA,SAASrB,kBAAkBG,KAAkC;IAC3D,OAAOA,UAAUtC;AACnB"}
|