@shipfox/api-agent-dto 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +48 -0
  5. package/LICENSE +21 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/schemas/catalog.d.ts +190 -0
  11. package/dist/schemas/catalog.d.ts.map +1 -0
  12. package/dist/schemas/catalog.js +231 -0
  13. package/dist/schemas/catalog.js.map +1 -0
  14. package/dist/schemas/custom-model-provider.d.ts +187 -0
  15. package/dist/schemas/custom-model-provider.d.ts.map +1 -0
  16. package/dist/schemas/custom-model-provider.js +168 -0
  17. package/dist/schemas/custom-model-provider.js.map +1 -0
  18. package/dist/schemas/harness.d.ts +49 -0
  19. package/dist/schemas/harness.d.ts.map +1 -0
  20. package/dist/schemas/harness.js +135 -0
  21. package/dist/schemas/harness.js.map +1 -0
  22. package/dist/schemas/index.d.ts +9 -0
  23. package/dist/schemas/index.d.ts.map +1 -0
  24. package/dist/schemas/index.js +10 -0
  25. package/dist/schemas/index.js.map +1 -0
  26. package/dist/schemas/materialized-agent-step-config.d.ts +186 -0
  27. package/dist/schemas/materialized-agent-step-config.d.ts.map +1 -0
  28. package/dist/schemas/materialized-agent-step-config.js +56 -0
  29. package/dist/schemas/materialized-agent-step-config.js.map +1 -0
  30. package/dist/schemas/model-provider-config.d.ts +108 -0
  31. package/dist/schemas/model-provider-config.d.ts.map +1 -0
  32. package/dist/schemas/model-provider-config.js +54 -0
  33. package/dist/schemas/model-provider-config.js.map +1 -0
  34. package/dist/schemas/model-provider-id.d.ts +81 -0
  35. package/dist/schemas/model-provider-id.d.ts.map +1 -0
  36. package/dist/schemas/model-provider-id.js +53 -0
  37. package/dist/schemas/model-provider-id.js.map +1 -0
  38. package/dist/schemas/runtime-config.d.ts +51 -0
  39. package/dist/schemas/runtime-config.d.ts.map +1 -0
  40. package/dist/schemas/runtime-config.js +31 -0
  41. package/dist/schemas/runtime-config.js.map +1 -0
  42. package/dist/schemas/workspace-defaults.d.ts +16 -0
  43. package/dist/schemas/workspace-defaults.d.ts.map +1 -0
  44. package/dist/schemas/workspace-defaults.js +10 -0
  45. package/dist/schemas/workspace-defaults.js.map +1 -0
  46. package/dist/tsconfig.test.tsbuildinfo +1 -0
  47. package/package.json +56 -0
  48. package/src/index.ts +122 -0
  49. package/src/schemas/catalog.test.ts +206 -0
  50. package/src/schemas/catalog.ts +304 -0
  51. package/src/schemas/custom-model-provider.test.ts +294 -0
  52. package/src/schemas/custom-model-provider.ts +258 -0
  53. package/src/schemas/harness.test.ts +133 -0
  54. package/src/schemas/harness.ts +210 -0
  55. package/src/schemas/index.ts +136 -0
  56. package/src/schemas/materialized-agent-step-config.test.ts +198 -0
  57. package/src/schemas/materialized-agent-step-config.ts +69 -0
  58. package/src/schemas/model-provider-config.test.ts +152 -0
  59. package/src/schemas/model-provider-config.ts +88 -0
  60. package/src/schemas/model-provider-id.ts +60 -0
  61. package/src/schemas/runtime-config.test.ts +132 -0
  62. package/src/schemas/runtime-config.ts +38 -0
  63. package/src/schemas/workspace-defaults.ts +14 -0
  64. package/tsconfig.build.json +9 -0
  65. package/tsconfig.build.tsbuildinfo +1 -0
  66. package/tsconfig.json +3 -0
  67. package/tsconfig.test.json +8 -0
  68. package/vitest.config.ts +3 -0
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@shipfox/api-agent-dto",
3
+ "license": "MIT",
4
+ "version": "2.0.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
8
+ "directory": "libs/api/agent-dto"
9
+ },
10
+ "private": false,
11
+ "type": "module",
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "imports": {
15
+ "#test/*": "./test/*",
16
+ "#*": {
17
+ "workspace-source": "./src/*",
18
+ "development": "./src/*",
19
+ "default": "./dist/*"
20
+ }
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "development": {
25
+ "types": "./src/index.ts",
26
+ "default": "./src/index.ts"
27
+ },
28
+ "default": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ }
32
+ }
33
+ },
34
+ "dependencies": {
35
+ "zod": "^4.4.3",
36
+ "@shipfox/workflow-document": "2.0.1"
37
+ },
38
+ "devDependencies": {
39
+ "@shipfox/biome": "1.8.1",
40
+ "@shipfox/depcruise": "1.0.1",
41
+ "@shipfox/swc": "1.2.5",
42
+ "@shipfox/typescript": "1.1.6",
43
+ "@shipfox/vitest": "1.2.2",
44
+ "@shipfox/ts-config": "1.3.8"
45
+ },
46
+ "scripts": {
47
+ "build": "shipfox-swc",
48
+ "check": "shipfox-biome-check",
49
+ "check:fix": "shipfox-biome-check --write",
50
+ "depcruise": "shipfox-depcruise",
51
+ "test": "shipfox-vitest-run",
52
+ "test:watch": "shipfox-vitest-watch",
53
+ "type": "shipfox-tsc-check",
54
+ "type:emit": "shipfox-tsc-emit"
55
+ }
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,122 @@
1
+ export {
2
+ AGENT_INTEGRATION_MCP_AUTH,
3
+ AGENT_INTEGRATION_MCP_ENDPOINT,
4
+ AGENT_INTEGRATION_MCP_SERVER_NAME,
5
+ AGENT_INTEGRATION_MCP_TRANSPORT,
6
+ type AgentIntegrationMcpServerConfigDto,
7
+ type AgentModelOptionDto,
8
+ type AgentRuntimeCredentialsResponseDto,
9
+ type AgentThinking,
10
+ agentIntegrationMcpServerSchema,
11
+ agentModelOptionSchema,
12
+ agentRuntimeCredentialsResponseSchema,
13
+ agentThinkingByHarness,
14
+ agentThinkingSchema,
15
+ buildHarnessToolDeploymentConfig,
16
+ CLAUDE_HARNESS,
17
+ type CreateCustomModelProviderBodyDto,
18
+ type CustomAgentModelDto,
19
+ type CustomModelProviderConfigDto,
20
+ type CustomModelProviderHeaderDto,
21
+ type CustomModelProviderHeaderRequestDto,
22
+ type CustomModelProviderRuntimeConfigDto,
23
+ claudeAgentThinkingSchema,
24
+ createCustomModelProviderBodySchema,
25
+ customAgentModelSchema,
26
+ customModelProviderConfigDtoSchema,
27
+ customModelProviderHeaderDtoSchema,
28
+ customModelProviderHeaderRequestSchema,
29
+ customModelProviderRuntimeConfigSchema,
30
+ DEFAULT_AGENT_THINKING,
31
+ DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW,
32
+ DEFAULT_CUSTOM_MODEL_INPUT_IMAGE,
33
+ DEFAULT_CUSTOM_MODEL_MAX_OUTPUT_TOKENS,
34
+ DEFAULT_CUSTOM_MODEL_REASONING,
35
+ DEFAULT_HARNESS,
36
+ DEFAULT_HARNESS_TOOL_DEPLOYMENT_CONFIG,
37
+ DEFAULT_MODEL_PROVIDER,
38
+ DEFAULT_PI_ENABLED_TOOL_PACKAGES,
39
+ DEFAULT_PI_WEB_SEARCH_ENABLED,
40
+ type DiscoverCustomModelProviderModelsBodyDto,
41
+ type DiscoverCustomModelProviderModelsBySlugBodyDto,
42
+ type DiscoverCustomModelProviderModelsResponseDto,
43
+ discoverCustomModelProviderModelsBodySchema,
44
+ discoverCustomModelProviderModelsBySlugBodySchema,
45
+ discoverCustomModelProviderModelsResponseSchema,
46
+ getHarnessDescriptor,
47
+ getHarnessToolDescriptor,
48
+ getModelProviderCredentialKeys,
49
+ getModelProviderEntry,
50
+ HARNESS_TOOL_PACKAGE_NAMES,
51
+ type Harness,
52
+ type HarnessDescriptor,
53
+ type HarnessToolDeploymentConfig,
54
+ type HarnessToolDescriptor,
55
+ type HarnessToolPackageName,
56
+ harnessSchema,
57
+ harnessSupportsProvider,
58
+ harnessSupportsTool,
59
+ isReservedModelProviderId,
60
+ type ListModelProviderConfigsResponseDto,
61
+ listEnabledHarnessTools,
62
+ listHarnessDescriptors,
63
+ listHarnessTools,
64
+ listModelProviderConfigsResponseSchema,
65
+ listSupportedModelProviders,
66
+ MAX_MODEL_COUNT,
67
+ type MaterializedAgentIntegrationConfigDto,
68
+ type MaterializedAgentIntegrationToolConfigDto,
69
+ type MaterializedAgentStepConfigDto,
70
+ MODEL_PROVIDER_CATALOG_SEED,
71
+ MODEL_PROVIDER_IDS,
72
+ MODEL_PROVIDER_SLUG_PATTERN,
73
+ type ModelProviderApi,
74
+ type ModelProviderCatalogEntryDto,
75
+ type ModelProviderCatalogResponseDto,
76
+ type ModelProviderCatalogSeedDto,
77
+ type ModelProviderConfigDto,
78
+ type ModelProviderConfigResponseDto,
79
+ type ModelProviderCredentialFieldDto,
80
+ type ModelProviderId,
81
+ type ModelProviderRef,
82
+ type ModelProviderSupportStatus,
83
+ materializedAgentIntegrationSchema,
84
+ materializedAgentIntegrationToolSchema,
85
+ materializedAgentStepConfigSchema,
86
+ modelProviderApiSchema,
87
+ modelProviderCatalogEntrySchema,
88
+ modelProviderCatalogResponseSchema,
89
+ modelProviderCatalogSeedSchema,
90
+ modelProviderConfigDtoSchema,
91
+ modelProviderConfigResponseSchema,
92
+ modelProviderCredentialFieldSchema,
93
+ modelProviderCredentialKeysMatch,
94
+ modelProviderRefSchema,
95
+ modelProviderSupportStatusSchema,
96
+ PI_HARNESS,
97
+ PI_HARNESS_TOOL_PACKAGE_NAMES,
98
+ parsePiEnabledToolPackages,
99
+ piAgentThinkingSchema,
100
+ providerIdSchema,
101
+ type SetDefaultHarnessBodyDto,
102
+ type SetDefaultHarnessResponseDto,
103
+ type SetDefaultModelProviderBodyDto,
104
+ type SetDefaultModelProviderResponseDto,
105
+ SUPPORTED_MODEL_PROVIDER_IDS,
106
+ type SupportedModelProviderId,
107
+ setDefaultHarnessBodySchema,
108
+ setDefaultHarnessResponseSchema,
109
+ setDefaultModelProviderBodySchema,
110
+ setDefaultModelProviderResponseSchema,
111
+ supportedModelProviderIdSchema,
112
+ thinkingLevelsForHarness,
113
+ UNSUPPORTED_MODEL_PROVIDER_IDS,
114
+ type UpdateCustomModelProviderBodyDto,
115
+ type UpdateCustomModelProviderHeaderRequestDto,
116
+ type UpdateModelProviderConfigBodyDto,
117
+ type UpdateModelProviderDefaultModelBodyDto,
118
+ updateCustomModelProviderBodySchema,
119
+ updateCustomModelProviderHeaderRequestSchema,
120
+ updateModelProviderConfigBodySchema,
121
+ updateModelProviderDefaultModelBodySchema,
122
+ } from '#schemas/index.js';
@@ -0,0 +1,206 @@
1
+ import {
2
+ agentThinkingSchema,
3
+ DEFAULT_AGENT_THINKING,
4
+ getModelProviderEntry,
5
+ listSupportedModelProviders,
6
+ MODEL_PROVIDER_CATALOG_SEED,
7
+ modelProviderCatalogEntrySchema,
8
+ modelProviderCatalogSeedSchema,
9
+ } from './catalog.js';
10
+ import {
11
+ MODEL_PROVIDER_IDS,
12
+ SUPPORTED_MODEL_PROVIDER_IDS,
13
+ UNSUPPORTED_MODEL_PROVIDER_IDS,
14
+ } from './model-provider-id.js';
15
+
16
+ describe('model provider catalog', () => {
17
+ it('parses the catalog seed', () => {
18
+ const parsed = modelProviderCatalogSeedSchema.array().parse(MODEL_PROVIDER_CATALOG_SEED);
19
+
20
+ expect(parsed).toHaveLength(35);
21
+ });
22
+
23
+ it('rejects a supported provider without a default model', () => {
24
+ const parse = () =>
25
+ modelProviderCatalogSeedSchema.parse({
26
+ id: 'openai',
27
+ label: 'OpenAI',
28
+ support_status: 'supported',
29
+ default_model: null,
30
+ credential_fields: [{key: 'api_key', label: 'API key', secret: true}],
31
+ unsupported_reason: null,
32
+ });
33
+
34
+ expect(parse).toThrow();
35
+ });
36
+
37
+ it('rejects a supported provider without credential fields', () => {
38
+ const parse = () =>
39
+ modelProviderCatalogSeedSchema.parse({
40
+ id: 'openai',
41
+ label: 'OpenAI',
42
+ support_status: 'supported',
43
+ default_model: 'gpt-5.5-pro',
44
+ credential_fields: [],
45
+ unsupported_reason: null,
46
+ });
47
+
48
+ expect(parse).toThrow();
49
+ });
50
+
51
+ it('rejects an unsupported provider with a default model', () => {
52
+ const parse = () =>
53
+ modelProviderCatalogSeedSchema.parse({
54
+ id: 'github-copilot',
55
+ label: 'GitHub Copilot',
56
+ support_status: 'unsupported',
57
+ default_model: 'gpt-5.5-pro',
58
+ credential_fields: [],
59
+ unsupported_reason: 'OAuth is not supported.',
60
+ });
61
+
62
+ expect(parse).toThrow();
63
+ });
64
+
65
+ it('rejects an unsupported provider with credential fields', () => {
66
+ const parse = () =>
67
+ modelProviderCatalogSeedSchema.parse({
68
+ id: 'github-copilot',
69
+ label: 'GitHub Copilot',
70
+ support_status: 'unsupported',
71
+ default_model: null,
72
+ credential_fields: [{key: 'api_key', label: 'API key', secret: true}],
73
+ unsupported_reason: 'OAuth is not supported.',
74
+ });
75
+
76
+ expect(parse).toThrow();
77
+ });
78
+
79
+ it('rejects a catalog entry whose support status disagrees with its provider id', () => {
80
+ const parse = () =>
81
+ modelProviderCatalogSeedSchema.parse({
82
+ id: 'github-copilot',
83
+ label: 'GitHub Copilot',
84
+ support_status: 'supported',
85
+ default_model: 'gpt-5.5-pro',
86
+ credential_fields: [{key: 'api_key', label: 'API key', secret: true}],
87
+ unsupported_reason: null,
88
+ });
89
+
90
+ expect(parse).toThrow();
91
+ });
92
+
93
+ it('keeps catalog ids unique and synced with provider id constants', () => {
94
+ const supportedIds = MODEL_PROVIDER_CATALOG_SEED.filter(
95
+ (entry) => entry.support_status === 'supported',
96
+ ).map((entry) => entry.id);
97
+ const unsupportedIds = MODEL_PROVIDER_CATALOG_SEED.filter(
98
+ (entry) => entry.support_status === 'unsupported',
99
+ ).map((entry) => entry.id);
100
+ const ids = [...supportedIds, ...unsupportedIds];
101
+
102
+ expect(supportedIds).toEqual([...SUPPORTED_MODEL_PROVIDER_IDS]);
103
+ expect(unsupportedIds).toEqual([...UNSUPPORTED_MODEL_PROVIDER_IDS]);
104
+ expect(ids).toEqual([...MODEL_PROVIDER_IDS]);
105
+ expect(new Set(ids).size).toBe(ids.length);
106
+ });
107
+
108
+ it('defines non-empty supported credential fields with at least one secret field', () => {
109
+ const supportedEntries = MODEL_PROVIDER_CATALOG_SEED.filter(
110
+ (entry) => entry.support_status === 'supported',
111
+ );
112
+
113
+ for (const entry of supportedEntries) {
114
+ expect(entry.credential_fields.length).toBeGreaterThan(0);
115
+ expect(entry.credential_fields.some((field) => field.secret)).toBe(true);
116
+ for (const field of entry.credential_fields) {
117
+ expect(field.key).not.toBe('');
118
+ expect(field.label).not.toBe('');
119
+ expect(typeof field.secret).toBe('boolean');
120
+ }
121
+ }
122
+ });
123
+
124
+ it('does not share credential field arrays between API-key providers', () => {
125
+ const anthropic = getModelProviderEntry('anthropic');
126
+ const openai = getModelProviderEntry('openai');
127
+ if (anthropic === undefined || openai === undefined) {
128
+ throw new Error('Missing API-key catalog entries');
129
+ }
130
+ const originalOpenAiFieldCount = openai.credential_fields.length;
131
+
132
+ try {
133
+ anthropic.credential_fields.push({key: 'extra', label: 'Extra', secret: true});
134
+
135
+ expect(openai.credential_fields).toHaveLength(originalOpenAiFieldCount);
136
+ } finally {
137
+ anthropic.credential_fields.pop();
138
+ }
139
+ });
140
+
141
+ it('parses catalog response entries assembled from seed entries and models', () => {
142
+ const responseEntries = MODEL_PROVIDER_CATALOG_SEED.map((entry) => ({
143
+ ...entry,
144
+ models:
145
+ entry.default_model === null
146
+ ? []
147
+ : [{id: entry.default_model, label: `${entry.label} default`}],
148
+ }));
149
+
150
+ const parsed = modelProviderCatalogEntrySchema.array().parse(responseEntries);
151
+
152
+ expect(parsed).toHaveLength(35);
153
+ });
154
+
155
+ it('rejects a supported response entry without models', () => {
156
+ const supportedEntry = getModelProviderEntry('openai');
157
+ if (supportedEntry === undefined) throw new Error('Missing OpenAI catalog entry');
158
+
159
+ const parse = () => modelProviderCatalogEntrySchema.parse({...supportedEntry, models: []});
160
+
161
+ expect(parse).toThrow();
162
+ });
163
+
164
+ it('rejects a supported response entry whose default model is not in models', () => {
165
+ const supportedEntry = getModelProviderEntry('openai');
166
+ if (supportedEntry === undefined) throw new Error('Missing OpenAI catalog entry');
167
+
168
+ const parse = () =>
169
+ modelProviderCatalogEntrySchema.parse({
170
+ ...supportedEntry,
171
+ models: [{id: 'not-the-default', label: 'Not the default'}],
172
+ });
173
+
174
+ expect(parse).toThrow();
175
+ });
176
+
177
+ it('rejects an unsupported response entry with models', () => {
178
+ const unsupportedEntry = getModelProviderEntry('github-copilot');
179
+ if (unsupportedEntry === undefined) throw new Error('Missing Copilot catalog entry');
180
+
181
+ const parse = () =>
182
+ modelProviderCatalogEntrySchema.parse({
183
+ ...unsupportedEntry,
184
+ models: [{id: 'gpt-5.5-pro', label: 'GPT-5.5 Pro'}],
185
+ });
186
+
187
+ expect(parse).toThrow();
188
+ });
189
+
190
+ it('finds catalog entries and lists only supported providers', () => {
191
+ const found = getModelProviderEntry('openai');
192
+ const missing = getModelProviderEntry('missing-provider');
193
+ const supportedEntries = listSupportedModelProviders();
194
+
195
+ expect(found?.id).toBe('openai');
196
+ expect(missing).toBeUndefined();
197
+ expect(supportedEntries).toHaveLength(31);
198
+ expect(supportedEntries.every((entry) => entry.support_status === 'supported')).toBe(true);
199
+ });
200
+
201
+ it('re-exports the instance-wide thinking contract', () => {
202
+ const parsed = agentThinkingSchema.parse(DEFAULT_AGENT_THINKING);
203
+
204
+ expect(parsed).toBe('xhigh');
205
+ });
206
+ });
@@ -0,0 +1,304 @@
1
+ import {
2
+ agentThinkingByHarness,
3
+ agentThinkingSchema,
4
+ claudeAgentThinkingSchema,
5
+ DEFAULT_AGENT_THINKING,
6
+ DEFAULT_HARNESS,
7
+ DEFAULT_MODEL_PROVIDER,
8
+ harnessSchema,
9
+ piAgentThinkingSchema,
10
+ thinkingLevelsForHarness,
11
+ } from '@shipfox/workflow-document';
12
+ import {z} from 'zod';
13
+ import {
14
+ type ModelProviderId,
15
+ providerIdSchema,
16
+ SUPPORTED_MODEL_PROVIDER_IDS,
17
+ type SupportedModelProviderId,
18
+ UNSUPPORTED_MODEL_PROVIDER_IDS,
19
+ } from './model-provider-id.js';
20
+
21
+ export type {AgentThinking, Harness} from '@shipfox/workflow-document';
22
+ export {
23
+ agentThinkingByHarness,
24
+ agentThinkingSchema,
25
+ claudeAgentThinkingSchema,
26
+ DEFAULT_AGENT_THINKING,
27
+ DEFAULT_HARNESS,
28
+ DEFAULT_MODEL_PROVIDER,
29
+ harnessSchema,
30
+ piAgentThinkingSchema,
31
+ thinkingLevelsForHarness,
32
+ };
33
+
34
+ export const agentModelOptionSchema = z.object({
35
+ id: z.string().min(1),
36
+ label: z.string().min(1),
37
+ });
38
+
39
+ export type AgentModelOptionDto = z.infer<typeof agentModelOptionSchema>;
40
+
41
+ export const modelProviderCredentialFieldSchema = z.object({
42
+ key: z.string().min(1),
43
+ label: z.string().min(1),
44
+ secret: z.boolean(),
45
+ });
46
+
47
+ export type ModelProviderCredentialFieldDto = z.infer<typeof modelProviderCredentialFieldSchema>;
48
+
49
+ export const modelProviderSupportStatusSchema = z.enum(['supported', 'unsupported']);
50
+
51
+ export type ModelProviderSupportStatus = z.infer<typeof modelProviderSupportStatusSchema>;
52
+
53
+ const supportedModelProviderIds = new Set<string>(SUPPORTED_MODEL_PROVIDER_IDS);
54
+ const unsupportedModelProviderIds = new Set<string>(UNSUPPORTED_MODEL_PROVIDER_IDS);
55
+
56
+ const modelProviderCatalogSeedBaseSchema = z.object({
57
+ id: providerIdSchema,
58
+ label: z.string().min(1),
59
+ support_status: modelProviderSupportStatusSchema,
60
+ default_model: z.string().min(1).nullable(),
61
+ credential_fields: z.array(modelProviderCredentialFieldSchema),
62
+ unsupported_reason: z.string().min(1).nullable(),
63
+ });
64
+
65
+ export const modelProviderCatalogSeedSchema =
66
+ modelProviderCatalogSeedBaseSchema.superRefine(validateCatalogSeedEntry);
67
+
68
+ export type ModelProviderCatalogSeedDto = z.infer<typeof modelProviderCatalogSeedSchema>;
69
+
70
+ export const modelProviderCatalogEntrySchema = modelProviderCatalogSeedBaseSchema
71
+ .extend({
72
+ models: z.array(agentModelOptionSchema),
73
+ })
74
+ .superRefine((entry, ctx) => {
75
+ validateCatalogSeedEntry(entry, ctx);
76
+
77
+ if (entry.support_status === 'supported') {
78
+ if (entry.models.length === 0) {
79
+ ctx.addIssue({
80
+ code: 'custom',
81
+ path: ['models'],
82
+ message: 'Supported model providers must include at least one model.',
83
+ });
84
+ }
85
+ if (
86
+ entry.default_model !== null &&
87
+ !entry.models.some((model) => model.id === entry.default_model)
88
+ ) {
89
+ ctx.addIssue({
90
+ code: 'custom',
91
+ path: ['default_model'],
92
+ message: 'Supported model provider default_model must be present in models.',
93
+ });
94
+ }
95
+ } else if (entry.models.length > 0) {
96
+ ctx.addIssue({
97
+ code: 'custom',
98
+ path: ['models'],
99
+ message: 'Unsupported model providers must not include models.',
100
+ });
101
+ }
102
+ });
103
+
104
+ export type ModelProviderCatalogEntryDto = z.infer<typeof modelProviderCatalogEntrySchema>;
105
+
106
+ export const modelProviderCatalogResponseSchema = z.object({
107
+ providers: z.array(modelProviderCatalogEntrySchema),
108
+ });
109
+
110
+ export type ModelProviderCatalogResponseDto = z.infer<typeof modelProviderCatalogResponseSchema>;
111
+
112
+ const apiKeyCredentialFields = [credentialField('api_key', 'API key', true)];
113
+
114
+ const azureCredentialFields = [
115
+ credentialField('endpoint', 'Endpoint', false),
116
+ credentialField('api_key', 'API key', true),
117
+ ];
118
+
119
+ const cloudflareAiGatewayCredentialFields = [
120
+ credentialField('api_key', 'API token', true),
121
+ credentialField('account_id', 'Account ID', false),
122
+ credentialField('gateway_id', 'Gateway ID', false),
123
+ ];
124
+
125
+ const cloudflareWorkersAiCredentialFields = [
126
+ credentialField('api_key', 'API token', true),
127
+ credentialField('account_id', 'Account ID', false),
128
+ ];
129
+
130
+ export const MODEL_PROVIDER_CATALOG_SEED: ModelProviderCatalogSeedDto[] = [
131
+ supportedProvider('anthropic', 'Anthropic', 'claude-opus-4-8'),
132
+ supportedProvider('ant-ling', 'Ant Ling', 'Ring-2.6-1T'),
133
+ supportedProvider('azure-openai-responses', 'Azure OpenAI', 'gpt-5.5-pro', azureCredentialFields),
134
+ supportedProvider('openai', 'OpenAI', 'gpt-5.5-pro'),
135
+ supportedProvider('deepseek', 'DeepSeek', 'deepseek-v4-pro'),
136
+ supportedProvider('nvidia', 'NVIDIA', 'nvidia/nemotron-3-ultra-550b-a55b'),
137
+ supportedProvider('google', 'Google AI Studio', 'gemini-3.1-pro-preview'),
138
+ supportedProvider('mistral', 'Mistral', 'mistral-large-latest'),
139
+ supportedProvider('groq', 'Groq', 'openai/gpt-oss-120b'),
140
+ supportedProvider('cerebras', 'Cerebras', 'gpt-oss-120b'),
141
+ supportedProvider(
142
+ 'cloudflare-ai-gateway',
143
+ 'Cloudflare AI Gateway',
144
+ 'claude-opus-4-8',
145
+ cloudflareAiGatewayCredentialFields,
146
+ ),
147
+ supportedProvider(
148
+ 'cloudflare-workers-ai',
149
+ 'Cloudflare Workers AI',
150
+ '@cf/moonshotai/kimi-k2.7-code',
151
+ cloudflareWorkersAiCredentialFields,
152
+ ),
153
+ supportedProvider('xai', 'xAI', 'grok-4.3'),
154
+ supportedProvider('openrouter', 'OpenRouter', 'anthropic/claude-opus-4.8'),
155
+ supportedProvider('vercel-ai-gateway', 'Vercel AI Gateway', 'anthropic/claude-opus-4.8'),
156
+ supportedProvider('zai', 'Z.ai', 'glm-5.2'),
157
+ supportedProvider('zai-coding-cn', 'Z.ai Coding CN', 'glm-5.2'),
158
+ supportedProvider('opencode', 'OpenCode', 'claude-opus-4-8'),
159
+ supportedProvider('opencode-go', 'OpenCode Go', 'kimi-k2.7-code'),
160
+ supportedProvider('huggingface', 'Hugging Face', 'deepseek-ai/DeepSeek-V4-Pro'),
161
+ supportedProvider('fireworks', 'Fireworks', 'accounts/fireworks/models/deepseek-v4-pro'),
162
+ supportedProvider('together', 'Together AI', 'deepseek-ai/DeepSeek-V4-Pro'),
163
+ supportedProvider('kimi-coding', 'Kimi Coding', 'k2p7'),
164
+ supportedProvider('minimax', 'MiniMax', 'MiniMax-M3'),
165
+ supportedProvider('minimax-cn', 'MiniMax CN', 'MiniMax-M3'),
166
+ supportedProvider('moonshotai', 'Moonshot AI', 'kimi-k2.7-code'),
167
+ supportedProvider('moonshotai-cn', 'Moonshot AI CN', 'kimi-k2.7-code'),
168
+ supportedProvider('xiaomi', 'Xiaomi', 'mimo-v2.5-pro'),
169
+ supportedProvider('xiaomi-token-plan-cn', 'Xiaomi Token Plan CN', 'mimo-v2.5-pro'),
170
+ supportedProvider('xiaomi-token-plan-ams', 'Xiaomi Token Plan AMS', 'mimo-v2.5-pro'),
171
+ supportedProvider('xiaomi-token-plan-sgp', 'Xiaomi Token Plan SGP', 'mimo-v2.5-pro'),
172
+ unsupportedProvider(
173
+ 'amazon-bedrock',
174
+ 'Amazon Bedrock',
175
+ 'AWS cloud credentials are not supported by workspace API-key provider configs yet.',
176
+ ),
177
+ unsupportedProvider(
178
+ 'google-vertex',
179
+ 'Google Vertex AI',
180
+ 'GCP cloud credentials are not supported by workspace API-key provider configs yet.',
181
+ ),
182
+ unsupportedProvider(
183
+ 'openai-codex',
184
+ 'OpenAI Codex',
185
+ 'ChatGPT subscription and OAuth credentials are not supported by workspace API-key provider configs yet.',
186
+ ),
187
+ unsupportedProvider(
188
+ 'github-copilot',
189
+ 'GitHub Copilot',
190
+ 'GitHub Copilot OAuth credentials are not supported by workspace API-key provider configs yet.',
191
+ ),
192
+ ];
193
+
194
+ export function getModelProviderEntry(id: string): ModelProviderCatalogSeedDto | undefined {
195
+ return MODEL_PROVIDER_CATALOG_SEED.find((entry) => entry.id === id);
196
+ }
197
+
198
+ export function listSupportedModelProviders(): ModelProviderCatalogSeedDto[] {
199
+ return MODEL_PROVIDER_CATALOG_SEED.filter((entry) => entry.support_status === 'supported');
200
+ }
201
+
202
+ function validateCatalogSeedEntry(
203
+ entry: z.infer<typeof modelProviderCatalogSeedBaseSchema>,
204
+ ctx: z.RefinementCtx,
205
+ ): void {
206
+ if (entry.support_status === 'supported') {
207
+ if (!supportedModelProviderIds.has(entry.id)) {
208
+ ctx.addIssue({
209
+ code: 'custom',
210
+ path: ['id'],
211
+ message: 'Supported catalog entries must use a supported model provider id.',
212
+ });
213
+ }
214
+ if (entry.default_model === null) {
215
+ ctx.addIssue({
216
+ code: 'custom',
217
+ path: ['default_model'],
218
+ message: 'Supported model providers must define a default_model.',
219
+ });
220
+ }
221
+ if (entry.credential_fields.length === 0) {
222
+ ctx.addIssue({
223
+ code: 'custom',
224
+ path: ['credential_fields'],
225
+ message: 'Supported model providers must define credential_fields.',
226
+ });
227
+ }
228
+ if (entry.unsupported_reason !== null) {
229
+ ctx.addIssue({
230
+ code: 'custom',
231
+ path: ['unsupported_reason'],
232
+ message: 'Supported model providers must not define unsupported_reason.',
233
+ });
234
+ }
235
+ } else {
236
+ if (!unsupportedModelProviderIds.has(entry.id)) {
237
+ ctx.addIssue({
238
+ code: 'custom',
239
+ path: ['id'],
240
+ message: 'Unsupported catalog entries must use an unsupported model provider id.',
241
+ });
242
+ }
243
+ if (entry.default_model !== null) {
244
+ ctx.addIssue({
245
+ code: 'custom',
246
+ path: ['default_model'],
247
+ message: 'Unsupported model providers must not define a default_model.',
248
+ });
249
+ }
250
+ if (entry.credential_fields.length > 0) {
251
+ ctx.addIssue({
252
+ code: 'custom',
253
+ path: ['credential_fields'],
254
+ message: 'Unsupported model providers must not define credential_fields.',
255
+ });
256
+ }
257
+ if (entry.unsupported_reason === null) {
258
+ ctx.addIssue({
259
+ code: 'custom',
260
+ path: ['unsupported_reason'],
261
+ message: 'Unsupported model providers must define unsupported_reason.',
262
+ });
263
+ }
264
+ }
265
+ }
266
+
267
+ function credentialField(
268
+ key: string,
269
+ label: string,
270
+ secret: boolean,
271
+ ): ModelProviderCredentialFieldDto {
272
+ return {key, label, secret};
273
+ }
274
+
275
+ function supportedProvider(
276
+ id: SupportedModelProviderId,
277
+ label: string,
278
+ defaultModel: string,
279
+ credentialFields: ModelProviderCredentialFieldDto[] = apiKeyCredentialFields,
280
+ ): ModelProviderCatalogSeedDto {
281
+ return {
282
+ id,
283
+ label,
284
+ support_status: 'supported',
285
+ default_model: defaultModel,
286
+ credential_fields: credentialFields.map((field) => ({...field})),
287
+ unsupported_reason: null,
288
+ };
289
+ }
290
+
291
+ function unsupportedProvider(
292
+ id: ModelProviderId,
293
+ label: string,
294
+ unsupportedReason: string,
295
+ ): ModelProviderCatalogSeedDto {
296
+ return {
297
+ id,
298
+ label,
299
+ support_status: 'unsupported',
300
+ default_model: null,
301
+ credential_fields: [],
302
+ unsupported_reason: unsupportedReason,
303
+ };
304
+ }