@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,25 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentThinking,
|
|
3
|
+
CustomAgentModelDto,
|
|
4
|
+
CustomModelProviderHeaderDto,
|
|
5
|
+
ModelProviderApi,
|
|
6
|
+
ModelProviderRef,
|
|
7
|
+
} from '@shipfox/api-agent-dto';
|
|
8
|
+
|
|
9
|
+
export interface ModelProviderConfig {
|
|
10
|
+
id: string;
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
providerId: ModelProviderRef;
|
|
13
|
+
kind: 'builtin' | 'custom';
|
|
14
|
+
displayName: string | null;
|
|
15
|
+
api: ModelProviderApi | null;
|
|
16
|
+
baseUrl: string | null;
|
|
17
|
+
headers: CustomModelProviderHeaderDto[] | null;
|
|
18
|
+
secretHeaderNames: string[] | null;
|
|
19
|
+
models: CustomAgentModelDto[] | null;
|
|
20
|
+
requiresApiKey: boolean;
|
|
21
|
+
defaultModel: string | null;
|
|
22
|
+
defaultThinking: AgentThinking;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
updatedAt: Date;
|
|
25
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentThinking,
|
|
3
|
+
Harness,
|
|
4
|
+
ModelProviderRef,
|
|
5
|
+
SupportedModelProviderId,
|
|
6
|
+
} from '@shipfox/api-agent-dto';
|
|
7
|
+
|
|
8
|
+
export class ModelProviderValidationError extends Error {
|
|
9
|
+
constructor(
|
|
10
|
+
public readonly providerId: ModelProviderRef,
|
|
11
|
+
public readonly sanitizedMessage: string,
|
|
12
|
+
) {
|
|
13
|
+
super(`Model provider validation failed for ${providerId}: ${sanitizedMessage}`);
|
|
14
|
+
this.name = 'ModelProviderValidationError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ModelProviderConfigNotFoundError extends Error {
|
|
19
|
+
constructor(
|
|
20
|
+
public readonly workspaceId: string,
|
|
21
|
+
public readonly providerId: string,
|
|
22
|
+
) {
|
|
23
|
+
super(`Model provider config not found: ${workspaceId}/${providerId}`);
|
|
24
|
+
this.name = 'ModelProviderConfigNotFoundError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class UnsupportedModelProviderError extends Error {
|
|
29
|
+
constructor(public readonly providerId: string) {
|
|
30
|
+
super(`Unsupported model provider: ${providerId}`);
|
|
31
|
+
this.name = 'UnsupportedModelProviderError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class UnsupportedHarnessProviderError extends Error {
|
|
36
|
+
constructor(
|
|
37
|
+
public readonly harness: Harness,
|
|
38
|
+
public readonly providerId: string,
|
|
39
|
+
public readonly supportedProviderIds: readonly string[],
|
|
40
|
+
) {
|
|
41
|
+
super(
|
|
42
|
+
`Harness ${harness} does not support model provider ${providerId}. Supported providers: ${supportedProviderIds.join(', ')}`,
|
|
43
|
+
);
|
|
44
|
+
this.name = 'UnsupportedHarnessProviderError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class UnsupportedHarnessThinkingError extends Error {
|
|
49
|
+
constructor(
|
|
50
|
+
public readonly harness: Harness,
|
|
51
|
+
public readonly thinking: AgentThinking,
|
|
52
|
+
public readonly supportedLevels: readonly AgentThinking[],
|
|
53
|
+
) {
|
|
54
|
+
super(
|
|
55
|
+
`Harness ${harness} does not support thinking ${thinking}. Supported levels: ${supportedLevels.join(', ')}`,
|
|
56
|
+
);
|
|
57
|
+
this.name = 'UnsupportedHarnessThinkingError';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class InvalidCredentialFieldsError extends Error {
|
|
62
|
+
constructor(public readonly providerId: SupportedModelProviderId) {
|
|
63
|
+
super(`Invalid credential fields for model provider: ${providerId}`);
|
|
64
|
+
this.name = 'InvalidCredentialFieldsError';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class InvalidAgentModelError extends Error {
|
|
69
|
+
public readonly harness: Harness;
|
|
70
|
+
public readonly providerId: ModelProviderRef;
|
|
71
|
+
public readonly model: string;
|
|
72
|
+
|
|
73
|
+
constructor(providerId: ModelProviderRef, model: string);
|
|
74
|
+
constructor(harness: Harness, providerId: ModelProviderRef, model: string);
|
|
75
|
+
constructor(
|
|
76
|
+
harnessOrProviderId: Harness | ModelProviderRef,
|
|
77
|
+
providerIdOrModel: ModelProviderRef | string,
|
|
78
|
+
model?: string,
|
|
79
|
+
) {
|
|
80
|
+
const harness = model === undefined ? 'pi' : (harnessOrProviderId as Harness);
|
|
81
|
+
const providerId =
|
|
82
|
+
model === undefined
|
|
83
|
+
? (harnessOrProviderId as ModelProviderRef)
|
|
84
|
+
: (providerIdOrModel as ModelProviderRef);
|
|
85
|
+
const resolvedModel = model ?? (providerIdOrModel as string);
|
|
86
|
+
|
|
87
|
+
super(`Model is not available for harness ${harness}: ${providerId}/${resolvedModel}`);
|
|
88
|
+
this.name = 'InvalidAgentModelError';
|
|
89
|
+
this.harness = harness;
|
|
90
|
+
this.providerId = providerId;
|
|
91
|
+
this.model = resolvedModel;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class ModelProviderValidationUnavailableError extends Error {
|
|
96
|
+
constructor(public readonly providerId: SupportedModelProviderId) {
|
|
97
|
+
super(`Model provider validation is not available for model provider: ${providerId}`);
|
|
98
|
+
this.name = 'ModelProviderValidationUnavailableError';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class CustomModelProviderSlugCollisionError extends Error {
|
|
103
|
+
constructor(
|
|
104
|
+
public readonly workspaceId: string,
|
|
105
|
+
public readonly providerId: ModelProviderRef,
|
|
106
|
+
) {
|
|
107
|
+
super(`Custom model provider slug already exists: ${workspaceId}/${providerId}`);
|
|
108
|
+
this.name = 'CustomModelProviderSlugCollisionError';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export class CustomModelProviderConfigNotFoundError extends Error {
|
|
113
|
+
constructor(
|
|
114
|
+
public readonly workspaceId: string,
|
|
115
|
+
public readonly providerId: ModelProviderRef,
|
|
116
|
+
) {
|
|
117
|
+
super(`Custom model provider config not found: ${workspaceId}/${providerId}`);
|
|
118
|
+
this.name = 'CustomModelProviderConfigNotFoundError';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class InvalidCustomModelProviderHeaderKeepError extends Error {
|
|
123
|
+
constructor(
|
|
124
|
+
public readonly providerId: ModelProviderRef,
|
|
125
|
+
public readonly headerName: string,
|
|
126
|
+
) {
|
|
127
|
+
super(`Custom model provider secret header cannot be kept: ${providerId}/${headerName}`);
|
|
128
|
+
this.name = 'InvalidCustomModelProviderHeaderKeepError';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class CustomModelProviderStoredSecretBaseUrlChangeError extends Error {
|
|
133
|
+
constructor(public readonly providerId: ModelProviderRef) {
|
|
134
|
+
super(
|
|
135
|
+
`Stored custom model provider secrets cannot be reused with a changed base URL: ${providerId}`,
|
|
136
|
+
);
|
|
137
|
+
this.name = 'CustomModelProviderStoredSecretBaseUrlChangeError';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {type AgentModelOptionDto, CLAUDE_HARNESS} from '@shipfox/api-agent-dto';
|
|
2
|
+
import {config} from '#config.js';
|
|
3
|
+
import type {HarnessProviderCatalog} from './registry.js';
|
|
4
|
+
|
|
5
|
+
const ANTHROPIC_MESSAGES_URL = 'https://api.anthropic.com/v1/messages';
|
|
6
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
7
|
+
const PROBE_MAX_TOKENS = 64;
|
|
8
|
+
const PROBE_PROMPT = 'Reply with OK.';
|
|
9
|
+
|
|
10
|
+
export const CLAUDE_MODEL_LINE: AgentModelOptionDto[] = [
|
|
11
|
+
{id: 'claude-haiku-4-5', label: 'Claude Haiku 4.5 (latest)'},
|
|
12
|
+
{id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5'},
|
|
13
|
+
{id: 'claude-opus-4-1', label: 'Claude Opus 4.1 (latest)'},
|
|
14
|
+
{id: 'claude-opus-4-1-20250805', label: 'Claude Opus 4.1'},
|
|
15
|
+
{id: 'claude-opus-4-5', label: 'Claude Opus 4.5 (latest)'},
|
|
16
|
+
{id: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5'},
|
|
17
|
+
{id: 'claude-opus-4-6', label: 'Claude Opus 4.6'},
|
|
18
|
+
{id: 'claude-opus-4-7', label: 'Claude Opus 4.7'},
|
|
19
|
+
{id: 'claude-opus-4-8', label: 'Claude Opus 4.8'},
|
|
20
|
+
{id: 'claude-sonnet-4-5', label: 'Claude Sonnet 4.5 (latest)'},
|
|
21
|
+
{id: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5'},
|
|
22
|
+
{id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6'},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export {CLAUDE_HARNESS};
|
|
26
|
+
|
|
27
|
+
export const claudeHarnessCatalog: HarnessProviderCatalog = {
|
|
28
|
+
listModels: () => CLAUDE_MODEL_LINE,
|
|
29
|
+
validateCredentials: probeClaudeCredentials,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function probeClaudeCredentials(params: {
|
|
33
|
+
model: string;
|
|
34
|
+
credentials: Record<string, string>;
|
|
35
|
+
signal?: AbortSignal | undefined;
|
|
36
|
+
}): Promise<void> {
|
|
37
|
+
const apiKey = params.credentials.api_key;
|
|
38
|
+
if (apiKey === undefined) throw new Error('Missing Anthropic API key credential.');
|
|
39
|
+
|
|
40
|
+
const response = await fetch(ANTHROPIC_MESSAGES_URL, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: claudeProbeHeaders(apiKey),
|
|
43
|
+
body: JSON.stringify(claudeProbeBody(params.model)),
|
|
44
|
+
redirect: 'error',
|
|
45
|
+
signal: timeoutSignal(params.signal, config.AGENT_PROVIDER_VALIDATION_TIMEOUT_MS),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
if (!response.ok) throw new Error(`Provider returned HTTP ${response.status}.`);
|
|
50
|
+
} finally {
|
|
51
|
+
await response.body?.cancel();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function claudeProbeHeaders(apiKey: string): Headers {
|
|
56
|
+
const headers = new Headers();
|
|
57
|
+
headers.set('content-type', 'application/json');
|
|
58
|
+
headers.set('x-api-key', apiKey);
|
|
59
|
+
headers.set('anthropic-version', ANTHROPIC_VERSION);
|
|
60
|
+
return headers;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function claudeProbeBody(model: string): unknown {
|
|
64
|
+
return {
|
|
65
|
+
model,
|
|
66
|
+
max_tokens: PROBE_MAX_TOKENS,
|
|
67
|
+
messages: [{role: 'user', content: PROBE_PROMPT}],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function timeoutSignal(signal: AbortSignal | undefined, timeoutMs: number): AbortSignal {
|
|
72
|
+
return signal === undefined
|
|
73
|
+
? AbortSignal.timeout(timeoutMs)
|
|
74
|
+
: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]);
|
|
75
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {CLAUDE_HARNESS, CLAUDE_MODEL_LINE, claudeHarnessCatalog} from './claude.js';
|
|
2
|
+
export {listPiProviderModels, PI_HARNESS, piHarnessCatalog} from './pi.js';
|
|
3
|
+
export {
|
|
4
|
+
getHarnessDescriptor,
|
|
5
|
+
getHarnessToolDescriptor,
|
|
6
|
+
type HarnessDescriptor,
|
|
7
|
+
type HarnessProviderCatalog,
|
|
8
|
+
type HarnessToolDeploymentConfig,
|
|
9
|
+
type HarnessToolDescriptor,
|
|
10
|
+
type HarnessToolPackageName,
|
|
11
|
+
harnessSupportsProvider,
|
|
12
|
+
harnessSupportsTool,
|
|
13
|
+
listEnabledHarnessTools,
|
|
14
|
+
listHarnessDescriptors,
|
|
15
|
+
listHarnessProviderModels,
|
|
16
|
+
listHarnessTools,
|
|
17
|
+
type ProbeHarnessProviderCredentialsParams,
|
|
18
|
+
probeHarnessProviderCredentials,
|
|
19
|
+
} from './registry.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {getModels, type KnownProvider} from '@earendil-works/pi-ai';
|
|
2
|
+
import {type AgentModelOptionDto, PI_HARNESS} from '@shipfox/api-agent-dto';
|
|
3
|
+
import {probeModelProviderCredentials} from '../model-provider-validation.js';
|
|
4
|
+
import type {HarnessProviderCatalog} from './registry.js';
|
|
5
|
+
|
|
6
|
+
export {PI_HARNESS};
|
|
7
|
+
|
|
8
|
+
export function listPiProviderModels(providerId: string): AgentModelOptionDto[] {
|
|
9
|
+
return getModels(providerId as KnownProvider).map((model) => ({
|
|
10
|
+
id: model.id,
|
|
11
|
+
label: model.name,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const piHarnessCatalog: HarnessProviderCatalog = {
|
|
16
|
+
listModels: listPiProviderModels,
|
|
17
|
+
validateCredentials: probeModelProviderCredentials,
|
|
18
|
+
};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import type {AssistantMessage, Context, ProviderStreamOptions} from '@earendil-works/pi-ai';
|
|
2
|
+
import {getModels} from '@earendil-works/pi-ai';
|
|
3
|
+
import {
|
|
4
|
+
claudeAgentThinkingSchema,
|
|
5
|
+
harnessSchema,
|
|
6
|
+
piAgentThinkingSchema,
|
|
7
|
+
} from '@shipfox/api-agent-dto';
|
|
8
|
+
import {
|
|
9
|
+
getHarnessDescriptor as getCoreHarnessDescriptor,
|
|
10
|
+
listHarnessProviderModels as listCoreHarnessProviderModels,
|
|
11
|
+
ModelProviderValidationError,
|
|
12
|
+
UnsupportedHarnessProviderError,
|
|
13
|
+
} from '#core/index.js';
|
|
14
|
+
import {CLAUDE_HARNESS, CLAUDE_MODEL_LINE} from './claude.js';
|
|
15
|
+
import {PI_HARNESS} from './pi.js';
|
|
16
|
+
import {
|
|
17
|
+
getHarnessDescriptor,
|
|
18
|
+
harnessSupportsProvider,
|
|
19
|
+
listHarnessDescriptors,
|
|
20
|
+
listHarnessProviderModels,
|
|
21
|
+
probeHarnessProviderCredentials,
|
|
22
|
+
} from './registry.js';
|
|
23
|
+
|
|
24
|
+
const piAi = vi.hoisted(() => ({
|
|
25
|
+
complete: vi.fn(),
|
|
26
|
+
getModels: vi.fn(),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const metrics = vi.hoisted(() => ({
|
|
30
|
+
modelProviderValidationCount: {
|
|
31
|
+
add: vi.fn(),
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock('@earendil-works/pi-ai', () => piAi);
|
|
36
|
+
vi.mock('#metrics/index.js', () => metrics);
|
|
37
|
+
|
|
38
|
+
describe('harness registry', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
piAi.getModels.mockImplementation((providerId: string) =>
|
|
42
|
+
providerId === 'anthropic' ? anthropicModels() : [],
|
|
43
|
+
);
|
|
44
|
+
piAi.complete.mockResolvedValue(createAssistantMessage());
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns the pi and Claude descriptors', () => {
|
|
48
|
+
expect(getHarnessDescriptor('pi')).toEqual({
|
|
49
|
+
id: 'pi',
|
|
50
|
+
label: 'pi',
|
|
51
|
+
description: 'Works with 30+ model providers',
|
|
52
|
+
supportedProviderIds: expect.arrayContaining(['anthropic', 'openai']),
|
|
53
|
+
thinkingLevels: piAgentThinkingSchema.options,
|
|
54
|
+
defaultThinking: 'xhigh',
|
|
55
|
+
defaultProviderId: 'anthropic',
|
|
56
|
+
tools: expect.arrayContaining([
|
|
57
|
+
expect.objectContaining({name: 'read'}),
|
|
58
|
+
expect.objectContaining({name: 'web_search'}),
|
|
59
|
+
]),
|
|
60
|
+
});
|
|
61
|
+
expect(getHarnessDescriptor('claude')).toEqual({
|
|
62
|
+
id: 'claude',
|
|
63
|
+
label: 'Claude',
|
|
64
|
+
description: 'Runs on your Anthropic API key',
|
|
65
|
+
supportedProviderIds: ['anthropic'],
|
|
66
|
+
thinkingLevels: claudeAgentThinkingSchema.options,
|
|
67
|
+
defaultThinking: 'xhigh',
|
|
68
|
+
defaultProviderId: 'anthropic',
|
|
69
|
+
tools: expect.arrayContaining([
|
|
70
|
+
expect.objectContaining({name: 'Read'}),
|
|
71
|
+
expect.objectContaining({name: 'WebSearch'}),
|
|
72
|
+
]),
|
|
73
|
+
});
|
|
74
|
+
expect(listHarnessDescriptors().map((descriptor) => descriptor.id)).toEqual(['pi', 'claude']);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('registers every harness declared by the shared schema', () => {
|
|
78
|
+
const registeredHarnessIds = listHarnessDescriptors().map((descriptor) => descriptor.id);
|
|
79
|
+
|
|
80
|
+
expect(registeredHarnessIds).toEqual(harnessSchema.options);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('keeps harness descriptor invariants synced with their source registries', () => {
|
|
84
|
+
const anthropicModelIds = new Set(getModels('anthropic').map((model) => model.id));
|
|
85
|
+
const unknownClaudeModelIds = CLAUDE_MODEL_LINE.filter(
|
|
86
|
+
(model) => !anthropicModelIds.has(model.id),
|
|
87
|
+
).map((model) => model.id);
|
|
88
|
+
|
|
89
|
+
expect(PI_HARNESS.thinkingLevels).toBe(piAgentThinkingSchema.options);
|
|
90
|
+
expect(CLAUDE_HARNESS.defaultThinking).toBe('xhigh');
|
|
91
|
+
expect(unknownClaudeModelIds).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('lists pi provider models from pi-ai', () => {
|
|
95
|
+
const models = listHarnessProviderModels('pi', 'anthropic');
|
|
96
|
+
|
|
97
|
+
expect(piAi.getModels).toHaveBeenCalledWith('anthropic');
|
|
98
|
+
expect(models).toContainEqual({id: 'claude-opus-4-8', label: 'Claude Opus 4.8'});
|
|
99
|
+
expect(models).toContainEqual({id: 'claude-haiku-4-5', label: 'Claude Haiku 4.5 (latest)'});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('lists the Claude model line for Anthropic', () => {
|
|
103
|
+
const models = listHarnessProviderModels('claude', 'anthropic');
|
|
104
|
+
|
|
105
|
+
expect(models).toContainEqual({id: 'claude-opus-4-8', label: 'Claude Opus 4.8'});
|
|
106
|
+
expect(models).toEqual(CLAUDE_MODEL_LINE);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('rejects unsupported harness/provider pairs before entering the catalog', () => {
|
|
110
|
+
expect(harnessSupportsProvider('claude', 'openai')).toBe(false);
|
|
111
|
+
expect(() => listHarnessProviderModels('claude', 'openai')).toThrow(
|
|
112
|
+
UnsupportedHarnessProviderError,
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('exports the public seam through the core barrel', () => {
|
|
117
|
+
expect(getCoreHarnessDescriptor('claude')).toBe(getHarnessDescriptor('claude'));
|
|
118
|
+
expect(listCoreHarnessProviderModels('claude', 'anthropic')).toEqual(CLAUDE_MODEL_LINE);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('probes pi credentials through pi-ai complete', async () => {
|
|
122
|
+
await probeHarnessProviderCredentials({
|
|
123
|
+
harness: 'pi',
|
|
124
|
+
providerId: 'anthropic',
|
|
125
|
+
model: 'claude-opus-4-8',
|
|
126
|
+
credentials: {api_key: 'sk-ant-secret'},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const [model, context, options] = piAi.complete.mock.calls[0] as [
|
|
130
|
+
{id: string},
|
|
131
|
+
Context,
|
|
132
|
+
ProviderStreamOptions,
|
|
133
|
+
];
|
|
134
|
+
expect(model.id).toBe('claude-opus-4-8');
|
|
135
|
+
expect(context.messages).toHaveLength(1);
|
|
136
|
+
expect(options).toMatchObject({apiKey: 'sk-ant-secret', maxRetries: 0});
|
|
137
|
+
expect(metrics.modelProviderValidationCount.add).toHaveBeenCalledWith(1, {
|
|
138
|
+
model_provider: 'anthropic',
|
|
139
|
+
outcome: 'succeeded',
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('wraps pi probe failures in sanitized validation errors', async () => {
|
|
144
|
+
piAi.complete.mockResolvedValue(
|
|
145
|
+
createAssistantMessage({
|
|
146
|
+
stopReason: 'error',
|
|
147
|
+
errorMessage: 'Authentication failed for sk-ant-secret',
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const probe = probeHarnessProviderCredentials({
|
|
152
|
+
harness: 'pi',
|
|
153
|
+
providerId: 'anthropic',
|
|
154
|
+
model: 'claude-opus-4-8',
|
|
155
|
+
credentials: {api_key: 'sk-ant-secret'},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await expect(probe).rejects.toMatchObject({
|
|
159
|
+
providerId: 'anthropic',
|
|
160
|
+
sanitizedMessage: expect.not.stringContaining('sk-ant-secret'),
|
|
161
|
+
});
|
|
162
|
+
await expect(probe).rejects.toThrow(ModelProviderValidationError);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('probes Claude credentials against Anthropic messages', async () => {
|
|
166
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(new Response('{}'));
|
|
167
|
+
|
|
168
|
+
await probeHarnessProviderCredentials({
|
|
169
|
+
harness: 'claude',
|
|
170
|
+
providerId: 'anthropic',
|
|
171
|
+
model: 'claude-opus-4-8',
|
|
172
|
+
credentials: {api_key: 'sk-ant-secret'},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const [, init] = fetchSpy.mock.calls[0] as [string, RequestInit];
|
|
176
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
177
|
+
'https://api.anthropic.com/v1/messages',
|
|
178
|
+
expect.objectContaining({method: 'POST', redirect: 'error'}),
|
|
179
|
+
);
|
|
180
|
+
expect((init.headers as Headers).get('x-api-key')).toBe('sk-ant-secret');
|
|
181
|
+
expect((init.headers as Headers).get('anthropic-version')).toBe('2023-06-01');
|
|
182
|
+
expect(JSON.parse(init.body as string)).toEqual({
|
|
183
|
+
model: 'claude-opus-4-8',
|
|
184
|
+
max_tokens: 64,
|
|
185
|
+
messages: [{role: 'user', content: 'Reply with OK.'}],
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('wraps Claude non-2xx responses as sanitized validation errors', async () => {
|
|
190
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(new Response('{}', {status: 401}));
|
|
191
|
+
|
|
192
|
+
const probe = probeHarnessProviderCredentials({
|
|
193
|
+
harness: 'claude',
|
|
194
|
+
providerId: 'anthropic',
|
|
195
|
+
model: 'claude-opus-4-8',
|
|
196
|
+
credentials: {api_key: 'sk-ant-secret'},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await expect(probe).rejects.toMatchObject({
|
|
200
|
+
providerId: 'anthropic',
|
|
201
|
+
sanitizedMessage: 'Provider returned HTTP 401.',
|
|
202
|
+
});
|
|
203
|
+
await expect(probe).rejects.toThrow(ModelProviderValidationError);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('rejects unsupported probes before recording validation metrics', async () => {
|
|
207
|
+
const probe = probeHarnessProviderCredentials({
|
|
208
|
+
harness: 'claude',
|
|
209
|
+
providerId: 'openai',
|
|
210
|
+
model: 'gpt-5.5-pro',
|
|
211
|
+
credentials: {api_key: 'sk-openai-secret'},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await expect(probe).rejects.toThrow(UnsupportedHarnessProviderError);
|
|
215
|
+
expect(metrics.modelProviderValidationCount.add).not.toHaveBeenCalled();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
function anthropicModels() {
|
|
220
|
+
return [
|
|
221
|
+
{
|
|
222
|
+
id: 'claude-opus-4-8',
|
|
223
|
+
provider: 'anthropic',
|
|
224
|
+
api: 'anthropic-messages',
|
|
225
|
+
name: 'Claude Opus 4.8',
|
|
226
|
+
baseUrl: 'https://api.anthropic.com',
|
|
227
|
+
reasoning: true,
|
|
228
|
+
input: ['text'],
|
|
229
|
+
cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0},
|
|
230
|
+
contextWindow: 200000,
|
|
231
|
+
maxTokens: 32000,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: 'claude-haiku-4-5',
|
|
235
|
+
provider: 'anthropic',
|
|
236
|
+
api: 'anthropic-messages',
|
|
237
|
+
name: 'Claude Haiku 4.5 (latest)',
|
|
238
|
+
baseUrl: 'https://api.anthropic.com',
|
|
239
|
+
reasoning: true,
|
|
240
|
+
input: ['text'],
|
|
241
|
+
cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0},
|
|
242
|
+
contextWindow: 200000,
|
|
243
|
+
maxTokens: 32000,
|
|
244
|
+
},
|
|
245
|
+
...CLAUDE_MODEL_LINE.filter(
|
|
246
|
+
(model) => model.id !== 'claude-opus-4-8' && model.id !== 'claude-haiku-4-5',
|
|
247
|
+
).map((model) => ({
|
|
248
|
+
id: model.id,
|
|
249
|
+
provider: 'anthropic',
|
|
250
|
+
api: 'anthropic-messages',
|
|
251
|
+
name: model.label,
|
|
252
|
+
baseUrl: 'https://api.anthropic.com',
|
|
253
|
+
reasoning: true,
|
|
254
|
+
input: ['text'],
|
|
255
|
+
cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0},
|
|
256
|
+
contextWindow: 200000,
|
|
257
|
+
maxTokens: 32000,
|
|
258
|
+
})),
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function createAssistantMessage(overrides: Partial<AssistantMessage> = {}): AssistantMessage {
|
|
263
|
+
return {
|
|
264
|
+
role: 'assistant',
|
|
265
|
+
content: [{type: 'text', text: 'OK'}],
|
|
266
|
+
api: 'anthropic-messages',
|
|
267
|
+
provider: 'anthropic',
|
|
268
|
+
model: 'claude-opus-4-8',
|
|
269
|
+
usage: {
|
|
270
|
+
input: 0,
|
|
271
|
+
output: 0,
|
|
272
|
+
cacheRead: 0,
|
|
273
|
+
cacheWrite: 0,
|
|
274
|
+
totalTokens: 0,
|
|
275
|
+
cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0},
|
|
276
|
+
},
|
|
277
|
+
stopReason: 'stop',
|
|
278
|
+
timestamp: Date.now(),
|
|
279
|
+
...overrides,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentModelOptionDto,
|
|
3
|
+
Harness,
|
|
4
|
+
HarnessDescriptor,
|
|
5
|
+
HarnessToolDeploymentConfig,
|
|
6
|
+
HarnessToolDescriptor,
|
|
7
|
+
HarnessToolPackageName,
|
|
8
|
+
SupportedModelProviderId,
|
|
9
|
+
} from '@shipfox/api-agent-dto';
|
|
10
|
+
import {
|
|
11
|
+
getHarnessDescriptor,
|
|
12
|
+
getHarnessToolDescriptor,
|
|
13
|
+
harnessSupportsProvider,
|
|
14
|
+
harnessSupportsTool,
|
|
15
|
+
listEnabledHarnessTools,
|
|
16
|
+
listHarnessDescriptors,
|
|
17
|
+
listHarnessTools,
|
|
18
|
+
SUPPORTED_MODEL_PROVIDER_IDS,
|
|
19
|
+
} from '@shipfox/api-agent-dto';
|
|
20
|
+
import {UnsupportedHarnessProviderError} from '../errors.js';
|
|
21
|
+
import {runProviderProbe} from '../model-provider-validation.js';
|
|
22
|
+
import {claudeHarnessCatalog} from './claude.js';
|
|
23
|
+
import {piHarnessCatalog} from './pi.js';
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
getHarnessDescriptor,
|
|
27
|
+
getHarnessToolDescriptor,
|
|
28
|
+
type HarnessDescriptor,
|
|
29
|
+
type HarnessToolDeploymentConfig,
|
|
30
|
+
type HarnessToolDescriptor,
|
|
31
|
+
type HarnessToolPackageName,
|
|
32
|
+
harnessSupportsProvider,
|
|
33
|
+
harnessSupportsTool,
|
|
34
|
+
listEnabledHarnessTools,
|
|
35
|
+
listHarnessDescriptors,
|
|
36
|
+
listHarnessTools,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export interface HarnessProviderCatalog {
|
|
40
|
+
listModels(providerId: string): AgentModelOptionDto[];
|
|
41
|
+
validateCredentials(params: {
|
|
42
|
+
providerId: string;
|
|
43
|
+
model: string;
|
|
44
|
+
credentials: Record<string, string>;
|
|
45
|
+
signal?: AbortSignal | undefined;
|
|
46
|
+
}): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ProbeHarnessProviderCredentialsParams {
|
|
50
|
+
harness: Harness;
|
|
51
|
+
providerId: string;
|
|
52
|
+
model: string;
|
|
53
|
+
credentials: Record<string, string>;
|
|
54
|
+
signal?: AbortSignal | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const CATALOGS: Record<Harness, HarnessProviderCatalog> = {
|
|
58
|
+
pi: piHarnessCatalog,
|
|
59
|
+
claude: claudeHarnessCatalog,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const supportedModelProviderIds = new Set<string>(SUPPORTED_MODEL_PROVIDER_IDS);
|
|
63
|
+
|
|
64
|
+
export function listHarnessProviderModels(
|
|
65
|
+
harness: Harness,
|
|
66
|
+
providerId: string,
|
|
67
|
+
): AgentModelOptionDto[] {
|
|
68
|
+
const descriptor = getHarnessDescriptor(harness);
|
|
69
|
+
assertHarnessSupportsProvider(descriptor, providerId);
|
|
70
|
+
return CATALOGS[harness].listModels(providerId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function probeHarnessProviderCredentials(
|
|
74
|
+
params: ProbeHarnessProviderCredentialsParams,
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
const descriptor = getHarnessDescriptor(params.harness);
|
|
77
|
+
assertHarnessSupportsProvider(descriptor, params.providerId);
|
|
78
|
+
|
|
79
|
+
await runProviderProbe({
|
|
80
|
+
probe: CATALOGS[params.harness].validateCredentials,
|
|
81
|
+
args: {
|
|
82
|
+
providerId: params.providerId,
|
|
83
|
+
model: params.model,
|
|
84
|
+
credentials: params.credentials,
|
|
85
|
+
...(params.signal ? {signal: params.signal} : {}),
|
|
86
|
+
},
|
|
87
|
+
metricLabel: metricLabel(params.providerId),
|
|
88
|
+
providerId: params.providerId,
|
|
89
|
+
secrets: Object.values(params.credentials),
|
|
90
|
+
...(params.signal ? {signal: params.signal} : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function assertHarnessSupportsProvider(descriptor: HarnessDescriptor, providerId: string): void {
|
|
95
|
+
if (descriptor.supportedProviderIds.includes(providerId)) return;
|
|
96
|
+
|
|
97
|
+
throw new UnsupportedHarnessProviderError(
|
|
98
|
+
descriptor.id,
|
|
99
|
+
providerId,
|
|
100
|
+
descriptor.supportedProviderIds,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function metricLabel(providerId: string): SupportedModelProviderId | 'custom' {
|
|
105
|
+
return supportedModelProviderIds.has(providerId)
|
|
106
|
+
? (providerId as SupportedModelProviderId)
|
|
107
|
+
: 'custom';
|
|
108
|
+
}
|