@shipfox/api-agent 5.0.0 → 6.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 +1 -1
- package/CHANGELOG.md +30 -0
- package/dist/core/custom-model-provider-config-service.d.ts +4 -0
- package/dist/core/custom-model-provider-config-service.d.ts.map +1 -1
- package/dist/core/custom-model-provider-config-service.js +19 -16
- package/dist/core/custom-model-provider-config-service.js.map +1 -1
- package/dist/core/model-provider-config-service.d.ts +4 -0
- package/dist/core/model-provider-config-service.d.ts.map +1 -1
- package/dist/core/model-provider-config-service.js +10 -8
- package/dist/core/model-provider-config-service.js.map +1 -1
- package/dist/core/resolve-runtime-credentials.d.ts +6 -2
- package/dist/core/resolve-runtime-credentials.d.ts.map +1 -1
- package/dist/core/resolve-runtime-credentials.js +10 -5
- package/dist/core/resolve-runtime-credentials.js.map +1 -1
- package/dist/core/secrets-client.d.ts +4 -0
- package/dist/core/secrets-client.d.ts.map +1 -0
- package/dist/core/secrets-client.js +6 -0
- package/dist/core/secrets-client.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -13
- package/dist/index.js.map +1 -1
- package/dist/presentation/e2eRoutes/create-model-provider.d.ts +2 -1
- package/dist/presentation/e2eRoutes/create-model-provider.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/create-model-provider.js +46 -45
- package/dist/presentation/e2eRoutes/create-model-provider.js.map +1 -1
- package/dist/presentation/e2eRoutes/index.d.ts +2 -0
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/index.js +9 -6
- package/dist/presentation/e2eRoutes/index.js.map +1 -1
- package/dist/presentation/index.d.ts +1 -1
- package/dist/presentation/index.d.ts.map +1 -1
- package/dist/presentation/index.js +1 -1
- package/dist/presentation/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +7 -0
- package/dist/presentation/inter-module.d.ts.map +1 -0
- package/dist/presentation/inter-module.js +45 -0
- package/dist/presentation/inter-module.js.map +1 -0
- package/dist/presentation/routes/create-custom-model-provider.d.ts +2 -1
- package/dist/presentation/routes/create-custom-model-provider.d.ts.map +1 -1
- package/dist/presentation/routes/create-custom-model-provider.js +39 -35
- package/dist/presentation/routes/create-custom-model-provider.js.map +1 -1
- package/dist/presentation/routes/delete-model-provider-config.d.ts +2 -1
- package/dist/presentation/routes/delete-model-provider-config.d.ts.map +1 -1
- package/dist/presentation/routes/delete-model-provider-config.js +33 -29
- package/dist/presentation/routes/delete-model-provider-config.js.map +1 -1
- package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.d.ts +2 -1
- package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.d.ts.map +1 -1
- package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.js +32 -28
- package/dist/presentation/routes/discover-custom-model-provider-models-by-slug.js.map +1 -1
- package/dist/presentation/routes/errors.d.ts.map +1 -1
- package/dist/presentation/routes/errors.js +13 -12
- package/dist/presentation/routes/errors.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -0
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +32 -29
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/update-custom-model-provider.d.ts +2 -1
- package/dist/presentation/routes/update-custom-model-provider.d.ts.map +1 -1
- package/dist/presentation/routes/update-custom-model-provider.js +41 -37
- package/dist/presentation/routes/update-custom-model-provider.js.map +1 -1
- package/dist/presentation/routes/upsert-model-provider-config.d.ts +2 -1
- package/dist/presentation/routes/upsert-model-provider-config.d.ts.map +1 -1
- package/dist/presentation/routes/upsert-model-provider-config.js +46 -42
- package/dist/presentation/routes/upsert-model-provider-config.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +11 -8
- package/src/core/custom-model-provider-config-service.ts +50 -34
- package/src/core/model-provider-config-service.test.ts +1 -3
- package/src/core/model-provider-config-service.ts +18 -15
- package/src/core/resolve-runtime-credentials.test.ts +3 -3
- package/src/core/resolve-runtime-credentials.ts +22 -8
- package/src/core/secrets-client.ts +13 -0
- package/src/index.ts +13 -8
- package/src/presentation/e2eRoutes/create-model-provider.ts +41 -39
- package/src/presentation/e2eRoutes/index.ts +6 -4
- package/src/presentation/index.ts +1 -1
- package/src/presentation/inter-module.ts +78 -0
- package/src/presentation/routes/create-custom-model-provider.ts +36 -30
- package/src/presentation/routes/delete-model-provider-config.ts +25 -22
- package/src/presentation/routes/discover-custom-model-provider-models-by-slug.ts +31 -25
- package/src/presentation/routes/errors.ts +11 -11
- package/src/presentation/routes/index.ts +32 -27
- package/src/presentation/routes/model-provider-config.test.ts +1 -1
- package/src/presentation/routes/update-custom-model-provider.ts +40 -34
- package/src/presentation/routes/upsert-model-provider-config.ts +43 -37
- package/test/fixtures/secrets-client.ts +80 -0
- package/test/globalSetup.ts +0 -9
- package/test/setup.ts +8 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-agent",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -42,22 +42,24 @@
|
|
|
42
42
|
"@opentelemetry/api": "^1.9.1",
|
|
43
43
|
"drizzle-orm": "^0.45.2",
|
|
44
44
|
"zod": "^4.4.3",
|
|
45
|
-
"@shipfox/api-agent-dto": "
|
|
46
|
-
"@shipfox/api-auth-context": "
|
|
47
|
-
"@shipfox/api-secrets": "
|
|
45
|
+
"@shipfox/api-agent-dto": "6.0.0",
|
|
46
|
+
"@shipfox/api-auth-context": "6.0.0",
|
|
47
|
+
"@shipfox/api-secrets-dto": "6.0.0",
|
|
48
|
+
"@shipfox/inter-module": "0.2.0",
|
|
48
49
|
"@shipfox/config": "1.2.2",
|
|
49
|
-
"@shipfox/node-drizzle": "0.3.
|
|
50
|
+
"@shipfox/node-drizzle": "0.3.2",
|
|
50
51
|
"@shipfox/node-egress-guard": "0.1.1",
|
|
51
|
-
"@shipfox/node-fastify": "0.2.
|
|
52
|
-
"@shipfox/node-module": "0.
|
|
52
|
+
"@shipfox/node-fastify": "0.2.4",
|
|
53
|
+
"@shipfox/node-module": "0.4.0",
|
|
53
54
|
"@shipfox/node-opentelemetry": "0.5.2",
|
|
54
55
|
"@shipfox/node-postgres": "0.4.2",
|
|
55
|
-
"@shipfox/redact": "0.2.
|
|
56
|
+
"@shipfox/redact": "0.2.3"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@types/pg": "^8.15.5",
|
|
59
60
|
"drizzle-kit": "^0.31.10",
|
|
60
61
|
"@shipfox/biome": "1.8.2",
|
|
62
|
+
"@shipfox/depcruise": "1.0.2",
|
|
61
63
|
"@shipfox/swc": "1.2.6",
|
|
62
64
|
"@shipfox/ts-config": "1.3.8",
|
|
63
65
|
"@shipfox/typescript": "1.1.7",
|
|
@@ -67,6 +69,7 @@
|
|
|
67
69
|
"build": "shipfox-swc",
|
|
68
70
|
"check": "shipfox-biome-check",
|
|
69
71
|
"check:fix": "shipfox-biome-check --write",
|
|
72
|
+
"depcruise": "shipfox-depcruise",
|
|
70
73
|
"test": "shipfox-vitest-run",
|
|
71
74
|
"test:watch": "shipfox-vitest-watch",
|
|
72
75
|
"type": "shipfox-tsc-check",
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
type UpdateCustomModelProviderBodyDto,
|
|
11
11
|
type UpdateCustomModelProviderHeaderRequestDto,
|
|
12
12
|
} from '@shipfox/api-agent-dto';
|
|
13
|
-
import {deleteSecrets, getSecretsByNamespace, setSecrets} from '@shipfox/api-secrets';
|
|
14
13
|
import {assertEgressAllowed} from '@shipfox/node-egress-guard';
|
|
15
14
|
import {
|
|
16
15
|
deleteModelProviderConfig,
|
|
@@ -38,6 +37,7 @@ import {
|
|
|
38
37
|
probeCustomModelProviderCredentials,
|
|
39
38
|
runProviderProbe,
|
|
40
39
|
} from './model-provider-validation.js';
|
|
40
|
+
import {type AgentSecretsClient, requireAgentSecretsClient} from './secrets-client.js';
|
|
41
41
|
|
|
42
42
|
export interface CreateCustomModelProviderConfigParams {
|
|
43
43
|
workspaceId: string;
|
|
@@ -55,6 +55,7 @@ export interface UpdateCustomModelProviderConfigParams {
|
|
|
55
55
|
|
|
56
56
|
export interface CustomModelProviderConfigServiceOptions {
|
|
57
57
|
probe?: typeof probeCustomModelProviderCredentials | undefined;
|
|
58
|
+
secrets?: AgentSecretsClient | undefined;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
interface SplitHeaders {
|
|
@@ -69,6 +70,7 @@ export async function createCustomModelProviderConfig(
|
|
|
69
70
|
options: CustomModelProviderConfigServiceOptions = {},
|
|
70
71
|
): Promise<ModelProviderConfig> {
|
|
71
72
|
const probe = options.probe ?? probeCustomModelProviderCredentials;
|
|
73
|
+
const secrets = requireAgentSecretsClient(options.secrets);
|
|
72
74
|
|
|
73
75
|
const existing = await getModelProviderConfig({
|
|
74
76
|
workspaceId: params.workspaceId,
|
|
@@ -123,7 +125,7 @@ export async function createCustomModelProviderConfig(
|
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
try {
|
|
126
|
-
await setSecrets({
|
|
128
|
+
await secrets.setSecrets({
|
|
127
129
|
workspaceId: params.workspaceId,
|
|
128
130
|
namespace: agentSystemNamespace(params.body.slug),
|
|
129
131
|
values: customCredentialsToStoreValues(secretCredentials),
|
|
@@ -134,7 +136,7 @@ export async function createCustomModelProviderConfig(
|
|
|
134
136
|
workspaceId: params.workspaceId,
|
|
135
137
|
providerId: params.body.slug,
|
|
136
138
|
});
|
|
137
|
-
await deleteSecrets({
|
|
139
|
+
await secrets.deleteSecrets({
|
|
138
140
|
workspaceId: params.workspaceId,
|
|
139
141
|
namespace: agentSystemNamespace(params.body.slug),
|
|
140
142
|
});
|
|
@@ -147,9 +149,10 @@ export async function updateCustomModelProviderConfig(
|
|
|
147
149
|
options: CustomModelProviderConfigServiceOptions = {},
|
|
148
150
|
): Promise<ModelProviderConfig> {
|
|
149
151
|
const probe = options.probe ?? probeCustomModelProviderCredentials;
|
|
152
|
+
const secrets = requireAgentSecretsClient(options.secrets);
|
|
150
153
|
|
|
151
154
|
const existing = await getExistingCustomConfig(params.workspaceId, params.providerId);
|
|
152
|
-
const existingSecrets = await getCustomProviderSecrets({
|
|
155
|
+
const existingSecrets = await getCustomProviderSecrets(secrets, {
|
|
153
156
|
workspaceId: params.workspaceId,
|
|
154
157
|
providerId: params.providerId,
|
|
155
158
|
});
|
|
@@ -204,7 +207,7 @@ export async function updateCustomModelProviderConfig(
|
|
|
204
207
|
});
|
|
205
208
|
|
|
206
209
|
const namespace = agentSystemNamespace(params.providerId);
|
|
207
|
-
await setSecrets({
|
|
210
|
+
await secrets.setSecrets({
|
|
208
211
|
workspaceId: params.workspaceId,
|
|
209
212
|
namespace,
|
|
210
213
|
values: customCredentialsToStoreValues(newSecretCredentials),
|
|
@@ -227,7 +230,7 @@ export async function updateCustomModelProviderConfig(
|
|
|
227
230
|
defaultThinking: existing.defaultThinking,
|
|
228
231
|
});
|
|
229
232
|
} catch (error) {
|
|
230
|
-
await rollbackCustomProviderSecretUpdate({
|
|
233
|
+
await rollbackCustomProviderSecretUpdate(secrets, {
|
|
231
234
|
workspaceId: params.workspaceId,
|
|
232
235
|
namespace,
|
|
233
236
|
existingSecrets,
|
|
@@ -236,7 +239,7 @@ export async function updateCustomModelProviderConfig(
|
|
|
236
239
|
throw error;
|
|
237
240
|
}
|
|
238
241
|
|
|
239
|
-
await pruneStaleCustomHeaderSecrets({
|
|
242
|
+
await pruneStaleCustomHeaderSecrets(secrets, {
|
|
240
243
|
workspaceId: params.workspaceId,
|
|
241
244
|
providerId: params.providerId,
|
|
242
245
|
existingSecrets,
|
|
@@ -247,13 +250,17 @@ export async function updateCustomModelProviderConfig(
|
|
|
247
250
|
return updated;
|
|
248
251
|
}
|
|
249
252
|
|
|
250
|
-
export async function resolveCustomModelProviderDiscoveryParams(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
export async function resolveCustomModelProviderDiscoveryParams(
|
|
254
|
+
params: {
|
|
255
|
+
workspaceId: string;
|
|
256
|
+
providerId: ModelProviderRef;
|
|
257
|
+
body: DiscoverCustomModelProviderModelsBySlugBodyDto;
|
|
258
|
+
},
|
|
259
|
+
options: {secrets?: AgentSecretsClient | undefined} = {},
|
|
260
|
+
): Promise<DiscoverCustomModelProviderModelsBodyDto> {
|
|
261
|
+
const secrets = requireAgentSecretsClient(options.secrets);
|
|
255
262
|
const existing = await getExistingCustomConfig(params.workspaceId, params.providerId);
|
|
256
|
-
const existingSecrets = await getCustomProviderSecrets({
|
|
263
|
+
const existingSecrets = await getCustomProviderSecrets(secrets, {
|
|
257
264
|
workspaceId: params.workspaceId,
|
|
258
265
|
providerId: params.providerId,
|
|
259
266
|
});
|
|
@@ -434,11 +441,14 @@ function selectProbeModel(
|
|
|
434
441
|
return model;
|
|
435
442
|
}
|
|
436
443
|
|
|
437
|
-
async function getCustomProviderSecrets(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
444
|
+
async function getCustomProviderSecrets(
|
|
445
|
+
secrets: AgentSecretsClient,
|
|
446
|
+
params: {
|
|
447
|
+
workspaceId: string;
|
|
448
|
+
providerId: ModelProviderRef;
|
|
449
|
+
},
|
|
450
|
+
): Promise<Record<string, string>> {
|
|
451
|
+
const {values} = await secrets.getSecretsByNamespace({
|
|
442
452
|
workspaceId: params.workspaceId,
|
|
443
453
|
namespace: agentSystemNamespace(params.providerId),
|
|
444
454
|
});
|
|
@@ -474,12 +484,15 @@ function normalizedUrl(value: string): string {
|
|
|
474
484
|
return normalizeCustomProviderBaseUrl(value);
|
|
475
485
|
}
|
|
476
486
|
|
|
477
|
-
async function rollbackCustomProviderSecretUpdate(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
487
|
+
async function rollbackCustomProviderSecretUpdate(
|
|
488
|
+
secrets: AgentSecretsClient,
|
|
489
|
+
params: {
|
|
490
|
+
workspaceId: string;
|
|
491
|
+
namespace: string;
|
|
492
|
+
existingSecrets: Record<string, string>;
|
|
493
|
+
attemptedSecrets: Record<string, string>;
|
|
494
|
+
},
|
|
495
|
+
): Promise<void> {
|
|
483
496
|
const attemptedKeys = Object.keys(params.attemptedSecrets);
|
|
484
497
|
if (attemptedKeys.length === 0) return;
|
|
485
498
|
|
|
@@ -490,7 +503,7 @@ async function rollbackCustomProviderSecretUpdate(params: {
|
|
|
490
503
|
}),
|
|
491
504
|
);
|
|
492
505
|
if (Object.keys(secretsToRestore).length > 0) {
|
|
493
|
-
await setSecrets({
|
|
506
|
+
await secrets.setSecrets({
|
|
494
507
|
workspaceId: params.workspaceId,
|
|
495
508
|
namespace: params.namespace,
|
|
496
509
|
values: customCredentialsToStoreValues(secretsToRestore),
|
|
@@ -500,20 +513,23 @@ async function rollbackCustomProviderSecretUpdate(params: {
|
|
|
500
513
|
const newKeys = attemptedKeys.filter((key) => params.existingSecrets[key] === undefined);
|
|
501
514
|
if (newKeys.length === 0) return;
|
|
502
515
|
|
|
503
|
-
await deleteSecrets({
|
|
516
|
+
await secrets.deleteSecrets({
|
|
504
517
|
workspaceId: params.workspaceId,
|
|
505
518
|
namespace: params.namespace,
|
|
506
519
|
keys: newKeys.map(customCredentialToStoreKey),
|
|
507
520
|
});
|
|
508
521
|
}
|
|
509
522
|
|
|
510
|
-
async function pruneStaleCustomHeaderSecrets(
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
523
|
+
async function pruneStaleCustomHeaderSecrets(
|
|
524
|
+
secrets: AgentSecretsClient,
|
|
525
|
+
params: {
|
|
526
|
+
workspaceId: string;
|
|
527
|
+
providerId: ModelProviderRef;
|
|
528
|
+
existingSecrets: Record<string, string>;
|
|
529
|
+
newSecrets: Record<string, string>;
|
|
530
|
+
replaceHeaders: boolean;
|
|
531
|
+
},
|
|
532
|
+
): Promise<void> {
|
|
517
533
|
if (!params.replaceHeaders) return;
|
|
518
534
|
|
|
519
535
|
const nextKeys = new Set(Object.keys(params.newSecrets));
|
|
@@ -522,7 +538,7 @@ async function pruneStaleCustomHeaderSecrets(params: {
|
|
|
522
538
|
);
|
|
523
539
|
if (staleKeys.length === 0) return;
|
|
524
540
|
|
|
525
|
-
await deleteSecrets({
|
|
541
|
+
await secrets.deleteSecrets({
|
|
526
542
|
workspaceId: params.workspaceId,
|
|
527
543
|
namespace: agentSystemNamespace(params.providerId),
|
|
528
544
|
keys: staleKeys.map(customCredentialToStoreKey),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import * as secretStore from '@shipfox/api-secrets';
|
|
3
2
|
import * as modelProviderDb from '#db/index.js';
|
|
4
3
|
import {getModelProviderConfig, upsertModelProviderConfig} from '#db/index.js';
|
|
4
|
+
import {getSecretsByNamespace, setSecrets} from '#test/fixtures/secrets-client.js';
|
|
5
5
|
import {agentSystemNamespace} from './credential-fingerprints.js';
|
|
6
6
|
import {
|
|
7
7
|
createCustomModelProviderConfig,
|
|
@@ -23,8 +23,6 @@ import {
|
|
|
23
23
|
updateModelProviderConfigDefaultModel,
|
|
24
24
|
} from './model-provider-config-service.js';
|
|
25
25
|
|
|
26
|
-
const {getSecretsByNamespace, setSecrets} = secretStore;
|
|
27
|
-
|
|
28
26
|
describe('testAndSaveModelProviderConfig', () => {
|
|
29
27
|
let workspaceId: string;
|
|
30
28
|
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
modelProviderCredentialKeysMatch,
|
|
6
6
|
type SupportedModelProviderId,
|
|
7
7
|
} from '@shipfox/api-agent-dto';
|
|
8
|
-
import {deleteSecrets, getSecretsByNamespace, setSecrets} from '@shipfox/api-secrets';
|
|
9
8
|
import {
|
|
10
9
|
deleteModelProviderConfig as deleteModelProviderConfigRow,
|
|
11
10
|
getModelProviderConfig,
|
|
@@ -22,6 +21,7 @@ import {
|
|
|
22
21
|
} from './errors.js';
|
|
23
22
|
import {buildModelProviderCatalog} from './model-provider-catalog.js';
|
|
24
23
|
import {probeModelProviderCredentials, runProviderProbe} from './model-provider-validation.js';
|
|
24
|
+
import {type AgentSecretsClient, requireAgentSecretsClient} from './secrets-client.js';
|
|
25
25
|
|
|
26
26
|
export interface TestAndSaveModelProviderConfigParams {
|
|
27
27
|
workspaceId: string;
|
|
@@ -35,6 +35,7 @@ export interface TestAndSaveModelProviderConfigParams {
|
|
|
35
35
|
|
|
36
36
|
export interface TestAndSaveModelProviderConfigOptions {
|
|
37
37
|
probe?: typeof probeModelProviderCredentials;
|
|
38
|
+
secrets?: AgentSecretsClient | undefined;
|
|
38
39
|
pruneStaleSecrets?:
|
|
39
40
|
| ((params: {workspaceId: string; namespace: string; expectedKeys: string[]}) => Promise<void>)
|
|
40
41
|
| undefined;
|
|
@@ -51,6 +52,7 @@ export async function testAndSaveModelProviderConfig(
|
|
|
51
52
|
options: TestAndSaveModelProviderConfigOptions = {},
|
|
52
53
|
): Promise<ModelProviderConfig> {
|
|
53
54
|
const probe = options.probe ?? probeModelProviderCredentials;
|
|
55
|
+
const secrets = requireAgentSecretsClient(options.secrets);
|
|
54
56
|
const entry = getModelProviderEntry(params.providerId);
|
|
55
57
|
if (entry === undefined || entry.support_status !== 'supported') {
|
|
56
58
|
throw new UnsupportedModelProviderError(params.providerId);
|
|
@@ -83,10 +85,11 @@ export async function testAndSaveModelProviderConfig(
|
|
|
83
85
|
secrets: Object.values(params.credentials),
|
|
84
86
|
signal: params.signal,
|
|
85
87
|
});
|
|
86
|
-
const pruneStaleSecrets =
|
|
88
|
+
const pruneStaleSecrets =
|
|
89
|
+
options.pruneStaleSecrets ?? ((input) => pruneStaleProviderCredentialSecrets(secrets, input));
|
|
87
90
|
const namespace = agentSystemNamespace(params.providerId);
|
|
88
91
|
const values = credentialsToStoreValues(params.providerId, params.credentials);
|
|
89
|
-
await setSecrets({
|
|
92
|
+
await secrets.setSecrets({
|
|
90
93
|
workspaceId: params.workspaceId,
|
|
91
94
|
namespace,
|
|
92
95
|
values,
|
|
@@ -154,12 +157,13 @@ export async function updateModelProviderConfigDefaultModel(
|
|
|
154
157
|
return config;
|
|
155
158
|
}
|
|
156
159
|
|
|
157
|
-
export async function deleteModelProviderConfig(
|
|
158
|
-
workspaceId: string;
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
export async function deleteModelProviderConfig(
|
|
161
|
+
params: {workspaceId: string; providerId: ModelProviderRef},
|
|
162
|
+
options: {secrets?: AgentSecretsClient | undefined} = {},
|
|
163
|
+
): Promise<boolean> {
|
|
164
|
+
const secrets = requireAgentSecretsClient(options.secrets);
|
|
161
165
|
const deleted = await deleteModelProviderConfigRow(params);
|
|
162
|
-
await deleteSecrets({
|
|
166
|
+
await secrets.deleteSecrets({
|
|
163
167
|
workspaceId: params.workspaceId,
|
|
164
168
|
namespace: agentSystemNamespace(params.providerId),
|
|
165
169
|
});
|
|
@@ -186,12 +190,11 @@ function resolveDefaultModel(
|
|
|
186
190
|
return {probeModel: model, storedModel: requestedModel ?? null};
|
|
187
191
|
}
|
|
188
192
|
|
|
189
|
-
async function pruneStaleProviderCredentialSecrets(
|
|
190
|
-
|
|
191
|
-
namespace: string;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const stored = await getSecretsByNamespace({
|
|
193
|
+
async function pruneStaleProviderCredentialSecrets(
|
|
194
|
+
secrets: AgentSecretsClient,
|
|
195
|
+
params: {workspaceId: string; namespace: string; expectedKeys: string[]},
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
const {values: stored} = await secrets.getSecretsByNamespace({
|
|
195
198
|
workspaceId: params.workspaceId,
|
|
196
199
|
namespace: params.namespace,
|
|
197
200
|
});
|
|
@@ -199,7 +202,7 @@ async function pruneStaleProviderCredentialSecrets(params: {
|
|
|
199
202
|
const staleKeys = Object.keys(stored).filter((key) => !expected.has(key));
|
|
200
203
|
if (staleKeys.length === 0) return;
|
|
201
204
|
|
|
202
|
-
await deleteSecrets({
|
|
205
|
+
await secrets.deleteSecrets({
|
|
203
206
|
workspaceId: params.workspaceId,
|
|
204
207
|
namespace: params.namespace,
|
|
205
208
|
keys: staleKeys,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {ModelProviderRef} from '@shipfox/api-agent-dto';
|
|
2
|
-
import {SecretDecryptionError, setSecrets} from '@shipfox/api-secrets';
|
|
3
2
|
import {deleteModelProviderConfig, upsertModelProviderConfig} from '#db/index.js';
|
|
3
|
+
import {setSecrets} from '#test/fixtures/secrets-client.js';
|
|
4
4
|
import {agentSystemNamespace, customCredentialsToStoreValues} from './credential-fingerprints.js';
|
|
5
5
|
import {ModelProviderConfigNotFoundError} from './errors.js';
|
|
6
6
|
import {resolveRuntimeCredentials} from './resolve-runtime-credentials.js';
|
|
@@ -245,7 +245,7 @@ describe('resolveRuntimeCredentials', () => {
|
|
|
245
245
|
});
|
|
246
246
|
|
|
247
247
|
it('does not expose credential material on store decryption errors', async () => {
|
|
248
|
-
const error = new
|
|
248
|
+
const error = new Error('Secret decryption failed');
|
|
249
249
|
await upsertModelProviderConfig({
|
|
250
250
|
workspaceId,
|
|
251
251
|
providerId: 'anthropic',
|
|
@@ -264,7 +264,7 @@ describe('resolveRuntimeCredentials', () => {
|
|
|
264
264
|
{getCredentialBag: vi.fn().mockRejectedValue(error)},
|
|
265
265
|
);
|
|
266
266
|
|
|
267
|
-
await expect(result).rejects.
|
|
267
|
+
await expect(result).rejects.toBe(error);
|
|
268
268
|
try {
|
|
269
269
|
await result;
|
|
270
270
|
} catch (error) {
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
modelProviderCredentialKeysMatch,
|
|
8
8
|
type SupportedModelProviderId,
|
|
9
9
|
} from '@shipfox/api-agent-dto';
|
|
10
|
-
import {
|
|
10
|
+
import {secretsInterModuleContract} from '@shipfox/api-secrets-dto/inter-module';
|
|
11
|
+
import {isInterModuleKnownError} from '@shipfox/inter-module';
|
|
11
12
|
import {config} from '#config.js';
|
|
12
13
|
import {getModelProviderConfig} from '#db/index.js';
|
|
13
14
|
import {agentRuntimeConfigResolvedCount} from '#metrics/index.js';
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
} from './credential-fingerprints.js';
|
|
19
20
|
import type {ModelProviderConfig} from './entities/model-provider-config.js';
|
|
20
21
|
import {ModelProviderConfigNotFoundError} from './errors.js';
|
|
22
|
+
import {type AgentSecretsClient, requireAgentSecretsClient} from './secrets-client.js';
|
|
21
23
|
|
|
22
24
|
export interface ResolveRuntimeCredentialsParams {
|
|
23
25
|
workspaceId: string;
|
|
@@ -34,7 +36,10 @@ interface RuntimeCredentialsConfig {
|
|
|
34
36
|
|
|
35
37
|
interface ResolveRuntimeCredentialsOptions {
|
|
36
38
|
runtimeConfig?: RuntimeCredentialsConfig | undefined;
|
|
37
|
-
|
|
39
|
+
secrets?: AgentSecretsClient | undefined;
|
|
40
|
+
getCredentialBag?:
|
|
41
|
+
| ((params: {workspaceId: string; namespace: string}) => Promise<Record<string, string>>)
|
|
42
|
+
| undefined;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export async function resolveRuntimeCredentials(
|
|
@@ -42,7 +47,7 @@ export async function resolveRuntimeCredentials(
|
|
|
42
47
|
options?: ResolveRuntimeCredentialsOptions,
|
|
43
48
|
): Promise<AgentRuntimeCredentialsResponseDto> {
|
|
44
49
|
const runtimeConfig = options?.runtimeConfig ?? config;
|
|
45
|
-
const
|
|
50
|
+
const secrets = options?.secrets;
|
|
46
51
|
const providerConfig = await getModelProviderConfig({
|
|
47
52
|
workspaceId: params.workspaceId,
|
|
48
53
|
providerId: params.provider,
|
|
@@ -50,10 +55,17 @@ export async function resolveRuntimeCredentials(
|
|
|
50
55
|
|
|
51
56
|
if (providerConfig) {
|
|
52
57
|
try {
|
|
53
|
-
const values =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
const values = options?.getCredentialBag
|
|
59
|
+
? await options.getCredentialBag({
|
|
60
|
+
workspaceId: params.workspaceId,
|
|
61
|
+
namespace: agentSystemNamespace(params.provider),
|
|
62
|
+
})
|
|
63
|
+
: (
|
|
64
|
+
await requireAgentSecretsClient(secrets).getSecretsByNamespace({
|
|
65
|
+
workspaceId: params.workspaceId,
|
|
66
|
+
namespace: agentSystemNamespace(params.provider),
|
|
67
|
+
})
|
|
68
|
+
).values;
|
|
57
69
|
if (providerConfig.kind === 'custom') {
|
|
58
70
|
const credentials = storeValuesToCustomRuntimeCredentials(values);
|
|
59
71
|
agentRuntimeConfigResolvedCount.add(1, {source: 'workspace', outcome: 'resolved'});
|
|
@@ -68,7 +80,9 @@ export async function resolveRuntimeCredentials(
|
|
|
68
80
|
agentRuntimeConfigResolvedCount.add(1, {source: 'workspace', outcome: 'resolved'});
|
|
69
81
|
return toResponse(params, credentials, providerConfig);
|
|
70
82
|
} catch (error) {
|
|
71
|
-
if (
|
|
83
|
+
if (
|
|
84
|
+
isInterModuleKnownError(secretsInterModuleContract.methods.getSecretsByNamespace, error)
|
|
85
|
+
) {
|
|
72
86
|
agentRuntimeConfigResolvedCount.add(1, {
|
|
73
87
|
source: 'workspace',
|
|
74
88
|
outcome: 'decryption_failed',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {SecretsInterModuleClient} from '@shipfox/api-secrets-dto/inter-module';
|
|
2
|
+
|
|
3
|
+
export type AgentSecretsClient = Pick<
|
|
4
|
+
SecretsInterModuleClient,
|
|
5
|
+
'deleteSecrets' | 'getSecretsByNamespace' | 'setSecrets'
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
export function requireAgentSecretsClient(
|
|
9
|
+
secrets: AgentSecretsClient | undefined,
|
|
10
|
+
): AgentSecretsClient {
|
|
11
|
+
if (secrets) return secrets;
|
|
12
|
+
throw new Error('Agent Secrets client is not configured.');
|
|
13
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type {ShipfoxModule} from '@shipfox/node-module';
|
|
2
|
+
import type {AgentSecretsClient} from '#core/secrets-client.js';
|
|
2
3
|
import {db, migrationsPath} from '#db/index.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import {createAgentE2eRoutes} from '#presentation/e2eRoutes/index.js';
|
|
5
|
+
import {createAgentInterModulePresentation} from '#presentation/inter-module.js';
|
|
6
|
+
import {createAgentRoutes} from '#presentation/routes/index.js';
|
|
5
7
|
|
|
6
8
|
export {
|
|
7
9
|
type AgentConfigResolutionContext,
|
|
@@ -35,9 +37,12 @@ export {
|
|
|
35
37
|
upsertModelProviderConfig,
|
|
36
38
|
} from '#db/index.js';
|
|
37
39
|
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
export function createAgentModule(params: {secrets: AgentSecretsClient}): ShipfoxModule {
|
|
41
|
+
return {
|
|
42
|
+
name: 'agent',
|
|
43
|
+
database: {db, migrationsPath},
|
|
44
|
+
routes: createAgentRoutes(params.secrets),
|
|
45
|
+
e2eRoutes: [createAgentE2eRoutes(params.secrets)],
|
|
46
|
+
interModulePresentations: [createAgentInterModulePresentation(params)],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {DEFAULT_AGENT_THINKING, getModelProviderEntry} from '@shipfox/api-agent-dto';
|
|
2
|
-
import {setSecrets} from '@shipfox/api-secrets';
|
|
3
2
|
import {ClientError, defineRoute} from '@shipfox/node-fastify';
|
|
4
3
|
import {z} from 'zod';
|
|
5
4
|
import {agentSystemNamespace} from '#core/credential-fingerprints.js';
|
|
6
5
|
import {listHarnessProviderModels} from '#core/harness/registry.js';
|
|
6
|
+
import type {AgentSecretsClient} from '#core/secrets-client.js';
|
|
7
7
|
import {upsertModelProviderConfig} from '#db/index.js';
|
|
8
8
|
|
|
9
9
|
const createE2eModelProviderBodySchema = z.object({
|
|
@@ -18,44 +18,46 @@ const createE2eModelProviderResponseSchema = z.object({
|
|
|
18
18
|
provider_id: z.literal('anthropic'),
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
export function createE2eModelProviderRoute(secrets: AgentSecretsClient) {
|
|
22
|
+
return defineRoute({
|
|
23
|
+
method: 'POST',
|
|
24
|
+
path: '/model-provider',
|
|
25
|
+
description: 'Create an Anthropic model provider config for E2E tests.',
|
|
26
|
+
schema: {
|
|
27
|
+
body: createE2eModelProviderBodySchema,
|
|
28
|
+
response: {201: createE2eModelProviderResponseSchema},
|
|
29
|
+
},
|
|
30
|
+
handler: async (request, reply) => {
|
|
31
|
+
const entry = getModelProviderEntry(request.body.provider_id);
|
|
32
|
+
if (entry === undefined || entry.default_model === null) {
|
|
33
|
+
throw new ClientError('Unsupported E2E model provider', 'unsupported-model-provider', {
|
|
34
|
+
status: 400,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const defaultModel = request.body.default_model ?? entry.default_model;
|
|
38
|
+
const supportedModels = listHarnessProviderModels('claude', request.body.provider_id);
|
|
39
|
+
if (!supportedModels.some((model) => model.id === defaultModel)) {
|
|
40
|
+
throw new ClientError('Unsupported Anthropic default model', 'unsupported-model', {
|
|
41
|
+
status: 400,
|
|
42
|
+
details: {default_model: defaultModel},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await secrets.setSecrets({
|
|
47
|
+
workspaceId: request.body.workspace_id,
|
|
48
|
+
namespace: agentSystemNamespace(request.body.provider_id),
|
|
49
|
+
values: {API_KEY: request.body.api_key},
|
|
34
50
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
details: {default_model: defaultModel},
|
|
51
|
+
await upsertModelProviderConfig({
|
|
52
|
+
workspaceId: request.body.workspace_id,
|
|
53
|
+
providerId: request.body.provider_id,
|
|
54
|
+
defaultModel,
|
|
55
|
+
defaultThinking: DEFAULT_AGENT_THINKING,
|
|
56
|
+
setAsDefault: request.body.set_as_default,
|
|
42
57
|
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
await setSecrets({
|
|
46
|
-
workspaceId: request.body.workspace_id,
|
|
47
|
-
namespace: agentSystemNamespace(request.body.provider_id),
|
|
48
|
-
values: {API_KEY: request.body.api_key},
|
|
49
|
-
});
|
|
50
|
-
await upsertModelProviderConfig({
|
|
51
|
-
workspaceId: request.body.workspace_id,
|
|
52
|
-
providerId: request.body.provider_id,
|
|
53
|
-
defaultModel,
|
|
54
|
-
defaultThinking: DEFAULT_AGENT_THINKING,
|
|
55
|
-
setAsDefault: request.body.set_as_default,
|
|
56
|
-
});
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
59
|
+
reply.code(201);
|
|
60
|
+
return {provider_id: request.body.provider_id};
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type {RouteGroup} from '@shipfox/node-fastify';
|
|
2
|
+
import type {AgentSecretsClient} from '#core/secrets-client.js';
|
|
2
3
|
import {createE2eModelProviderRoute} from './create-model-provider.js';
|
|
3
4
|
|
|
4
|
-
export
|
|
5
|
-
prefix: '/agent',
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export function createAgentE2eRoutes(secrets: AgentSecretsClient): RouteGroup {
|
|
6
|
+
return {prefix: '/agent', routes: [createE2eModelProviderRoute(secrets)]};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const agentE2eRoutes = createAgentE2eRoutes(undefined as unknown as AgentSecretsClient);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {agentRoutes as routes} from './routes/index.js';
|
|
1
|
+
export {agentRoutes as routes, createAgentRoutes} from './routes/index.js';
|