@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
@@ -0,0 +1,210 @@
1
+ import {type AgentThinking, agentThinkingByHarness, type Harness} from '@shipfox/workflow-document';
2
+ import {type ModelProviderRef, SUPPORTED_MODEL_PROVIDER_IDS} from './model-provider-id.js';
3
+
4
+ export const PI_HARNESS_TOOL_PACKAGE_NAMES = ['pi-web-access'] as const;
5
+ export const HARNESS_TOOL_PACKAGE_NAMES = [...PI_HARNESS_TOOL_PACKAGE_NAMES] as const;
6
+
7
+ export type HarnessToolPackageName = (typeof HARNESS_TOOL_PACKAGE_NAMES)[number];
8
+
9
+ export interface HarnessToolDescriptor {
10
+ readonly name: string;
11
+ readonly label: string;
12
+ readonly source: 'built_in' | 'package';
13
+ readonly packageName?: HarnessToolPackageName;
14
+ readonly enabledByDefault: boolean;
15
+ }
16
+
17
+ export interface HarnessToolDeploymentConfig {
18
+ readonly pi?: {
19
+ readonly enabledToolPackages?: readonly HarnessToolPackageName[];
20
+ readonly webSearchEnabled?: boolean;
21
+ };
22
+ readonly claude?: {
23
+ readonly enabledToolPackages?: readonly HarnessToolPackageName[];
24
+ };
25
+ }
26
+
27
+ export interface HarnessDescriptor {
28
+ readonly id: Harness;
29
+ readonly label: string;
30
+ readonly description: string;
31
+ readonly supportedProviderIds: readonly string[];
32
+ readonly thinkingLevels: readonly AgentThinking[];
33
+ readonly defaultThinking: AgentThinking;
34
+ readonly defaultProviderId: ModelProviderRef;
35
+ readonly tools: readonly HarnessToolDescriptor[];
36
+ }
37
+
38
+ export const DEFAULT_PI_ENABLED_TOOL_PACKAGES = 'pi-web-access';
39
+ export const DEFAULT_PI_WEB_SEARCH_ENABLED = true;
40
+
41
+ export const PI_HARNESS: HarnessDescriptor = {
42
+ id: 'pi',
43
+ label: 'pi',
44
+ description: 'Works with 30+ model providers',
45
+ supportedProviderIds: SUPPORTED_MODEL_PROVIDER_IDS,
46
+ thinkingLevels: agentThinkingByHarness.pi.options,
47
+ defaultThinking: 'xhigh',
48
+ defaultProviderId: 'anthropic',
49
+ tools: [
50
+ builtInTool('read', 'Read'),
51
+ builtInTool('bash', 'Bash'),
52
+ builtInTool('edit', 'Edit'),
53
+ builtInTool('write', 'Write'),
54
+ builtInTool('grep', 'Grep'),
55
+ builtInTool('find', 'Find'),
56
+ builtInTool('ls', 'List'),
57
+ packageTool('web_search', 'Web search', 'pi-web-access'),
58
+ packageTool('fetch_content', 'Fetch content', 'pi-web-access'),
59
+ packageTool('get_search_content', 'Get search content', 'pi-web-access'),
60
+ ],
61
+ };
62
+
63
+ export const CLAUDE_HARNESS: HarnessDescriptor = {
64
+ id: 'claude',
65
+ label: 'Claude',
66
+ description: 'Runs on your Anthropic API key',
67
+ supportedProviderIds: ['anthropic'],
68
+ thinkingLevels: agentThinkingByHarness.claude.options,
69
+ defaultThinking: 'xhigh',
70
+ defaultProviderId: 'anthropic',
71
+ tools: [
72
+ builtInTool('Read', 'Read'),
73
+ builtInTool('Bash', 'Bash'),
74
+ builtInTool('Edit', 'Edit'),
75
+ builtInTool('Write', 'Write'),
76
+ builtInTool('Glob', 'Glob'),
77
+ builtInTool('Grep', 'Grep'),
78
+ builtInTool('WebFetch', 'Web fetch'),
79
+ builtInTool('WebSearch', 'Web search'),
80
+ ],
81
+ };
82
+
83
+ const HARNESS_DESCRIPTORS = {
84
+ pi: PI_HARNESS,
85
+ claude: CLAUDE_HARNESS,
86
+ } as const satisfies Record<Harness, HarnessDescriptor>;
87
+
88
+ export function getHarnessDescriptor(id: Harness): HarnessDescriptor {
89
+ return HARNESS_DESCRIPTORS[id];
90
+ }
91
+
92
+ export function listHarnessDescriptors(): HarnessDescriptor[] {
93
+ return Object.values(HARNESS_DESCRIPTORS);
94
+ }
95
+
96
+ export function harnessSupportsProvider(id: Harness, providerId: string): boolean {
97
+ return getHarnessDescriptor(id).supportedProviderIds.includes(providerId);
98
+ }
99
+
100
+ export function listHarnessTools(id: Harness): HarnessToolDescriptor[] {
101
+ return [...getHarnessDescriptor(id).tools];
102
+ }
103
+
104
+ export function getHarnessToolDescriptor(
105
+ id: Harness,
106
+ toolName: string,
107
+ ): HarnessToolDescriptor | undefined {
108
+ return getHarnessDescriptor(id).tools.find((tool) => tool.name === toolName);
109
+ }
110
+
111
+ export function listEnabledHarnessTools(
112
+ id: Harness,
113
+ deploymentConfig: HarnessToolDeploymentConfig = {},
114
+ ): HarnessToolDescriptor[] {
115
+ return getHarnessDescriptor(id).tools.filter((tool) =>
116
+ isHarnessToolEnabled(id, tool, deploymentConfig),
117
+ );
118
+ }
119
+
120
+ export function harnessSupportsTool(
121
+ id: Harness,
122
+ toolName: string,
123
+ deploymentConfig: HarnessToolDeploymentConfig = {},
124
+ ): boolean {
125
+ const tool = getHarnessToolDescriptor(id, toolName);
126
+ if (tool === undefined) return false;
127
+
128
+ return isHarnessToolEnabled(id, tool, deploymentConfig);
129
+ }
130
+
131
+ export function buildHarnessToolDeploymentConfig(params: {
132
+ readonly piEnabledToolPackages?: string | undefined;
133
+ readonly piWebSearchEnabled?: boolean | undefined;
134
+ }): HarnessToolDeploymentConfig {
135
+ return {
136
+ pi: {
137
+ enabledToolPackages: parsePiEnabledToolPackages(
138
+ params.piEnabledToolPackages ?? DEFAULT_PI_ENABLED_TOOL_PACKAGES,
139
+ ),
140
+ webSearchEnabled: params.piWebSearchEnabled ?? DEFAULT_PI_WEB_SEARCH_ENABLED,
141
+ },
142
+ claude: {
143
+ enabledToolPackages: [],
144
+ },
145
+ };
146
+ }
147
+
148
+ export const DEFAULT_HARNESS_TOOL_DEPLOYMENT_CONFIG = buildHarnessToolDeploymentConfig({});
149
+
150
+ export function parsePiEnabledToolPackages(value: string): HarnessToolPackageName[] {
151
+ const packageNames = value
152
+ .split(',')
153
+ .map((packageName) => packageName.trim())
154
+ .filter((packageName) => packageName.length > 0);
155
+
156
+ const validPackageNames = new Set<string>(PI_HARNESS_TOOL_PACKAGE_NAMES);
157
+ const invalidPackageNames = packageNames.filter(
158
+ (packageName) => !validPackageNames.has(packageName),
159
+ );
160
+ if (invalidPackageNames.length > 0) {
161
+ throw new Error(
162
+ `AGENT_PI_ENABLED_TOOL_PACKAGES contains unsupported package(s): ${invalidPackageNames.join(
163
+ ', ',
164
+ )}. Accepted values: ${PI_HARNESS_TOOL_PACKAGE_NAMES.join(', ')}.`,
165
+ );
166
+ }
167
+
168
+ return [...new Set(packageNames)] as HarnessToolPackageName[];
169
+ }
170
+
171
+ function isHarnessToolEnabled(
172
+ id: Harness,
173
+ tool: HarnessToolDescriptor,
174
+ deploymentConfig: HarnessToolDeploymentConfig,
175
+ ): boolean {
176
+ if (!tool.enabledByDefault) return false;
177
+ if (tool.source === 'built_in') return true;
178
+
179
+ const packageName = tool.packageName;
180
+ if (packageName === undefined) return false;
181
+
182
+ const harnessConfig = deploymentConfig[id];
183
+ if (!harnessConfig?.enabledToolPackages?.includes(packageName)) return false;
184
+
185
+ return !isDisabledPiSearchTool(id, tool, deploymentConfig);
186
+ }
187
+
188
+ function isDisabledPiSearchTool(
189
+ id: Harness,
190
+ tool: HarnessToolDescriptor,
191
+ deploymentConfig: HarnessToolDeploymentConfig,
192
+ ): boolean {
193
+ return (
194
+ id === 'pi' &&
195
+ deploymentConfig.pi?.webSearchEnabled === false &&
196
+ (tool.name === 'web_search' || tool.name === 'get_search_content')
197
+ );
198
+ }
199
+
200
+ function builtInTool(name: string, label: string): HarnessToolDescriptor {
201
+ return {name, label, source: 'built_in', enabledByDefault: true};
202
+ }
203
+
204
+ function packageTool(
205
+ name: string,
206
+ label: string,
207
+ packageName: HarnessToolPackageName,
208
+ ): HarnessToolDescriptor {
209
+ return {name, label, source: 'package', packageName, enabledByDefault: true};
210
+ }
@@ -0,0 +1,136 @@
1
+ export {
2
+ type AgentModelOptionDto,
3
+ type AgentThinking,
4
+ agentModelOptionSchema,
5
+ agentThinkingByHarness,
6
+ agentThinkingSchema,
7
+ claudeAgentThinkingSchema,
8
+ DEFAULT_AGENT_THINKING,
9
+ DEFAULT_HARNESS,
10
+ DEFAULT_MODEL_PROVIDER,
11
+ getModelProviderEntry,
12
+ type Harness,
13
+ harnessSchema,
14
+ listSupportedModelProviders,
15
+ MODEL_PROVIDER_CATALOG_SEED,
16
+ type ModelProviderCatalogEntryDto,
17
+ type ModelProviderCatalogResponseDto,
18
+ type ModelProviderCatalogSeedDto,
19
+ type ModelProviderCredentialFieldDto,
20
+ type ModelProviderSupportStatus,
21
+ modelProviderCatalogEntrySchema,
22
+ modelProviderCatalogResponseSchema,
23
+ modelProviderCatalogSeedSchema,
24
+ modelProviderCredentialFieldSchema,
25
+ modelProviderSupportStatusSchema,
26
+ piAgentThinkingSchema,
27
+ thinkingLevelsForHarness,
28
+ } from './catalog.js';
29
+ export {
30
+ type CreateCustomModelProviderBodyDto,
31
+ type CustomAgentModelDto,
32
+ type CustomModelProviderConfigDto,
33
+ type CustomModelProviderHeaderDto,
34
+ type CustomModelProviderHeaderRequestDto,
35
+ type CustomModelProviderRuntimeConfigDto,
36
+ createCustomModelProviderBodySchema,
37
+ customAgentModelSchema,
38
+ customModelProviderConfigDtoSchema,
39
+ customModelProviderHeaderDtoSchema,
40
+ customModelProviderHeaderRequestSchema,
41
+ customModelProviderRuntimeConfigSchema,
42
+ DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW,
43
+ DEFAULT_CUSTOM_MODEL_INPUT_IMAGE,
44
+ DEFAULT_CUSTOM_MODEL_MAX_OUTPUT_TOKENS,
45
+ DEFAULT_CUSTOM_MODEL_REASONING,
46
+ type DiscoverCustomModelProviderModelsBodyDto,
47
+ type DiscoverCustomModelProviderModelsBySlugBodyDto,
48
+ type DiscoverCustomModelProviderModelsResponseDto,
49
+ discoverCustomModelProviderModelsBodySchema,
50
+ discoverCustomModelProviderModelsBySlugBodySchema,
51
+ discoverCustomModelProviderModelsResponseSchema,
52
+ MAX_MODEL_COUNT,
53
+ type ModelProviderApi,
54
+ modelProviderApiSchema,
55
+ type UpdateCustomModelProviderBodyDto,
56
+ type UpdateCustomModelProviderHeaderRequestDto,
57
+ updateCustomModelProviderBodySchema,
58
+ updateCustomModelProviderHeaderRequestSchema,
59
+ } from './custom-model-provider.js';
60
+ export {
61
+ buildHarnessToolDeploymentConfig,
62
+ CLAUDE_HARNESS,
63
+ DEFAULT_HARNESS_TOOL_DEPLOYMENT_CONFIG,
64
+ DEFAULT_PI_ENABLED_TOOL_PACKAGES,
65
+ DEFAULT_PI_WEB_SEARCH_ENABLED,
66
+ getHarnessDescriptor,
67
+ getHarnessToolDescriptor,
68
+ HARNESS_TOOL_PACKAGE_NAMES,
69
+ type HarnessDescriptor,
70
+ type HarnessToolDeploymentConfig,
71
+ type HarnessToolDescriptor,
72
+ type HarnessToolPackageName,
73
+ harnessSupportsProvider,
74
+ harnessSupportsTool,
75
+ listEnabledHarnessTools,
76
+ listHarnessDescriptors,
77
+ listHarnessTools,
78
+ PI_HARNESS,
79
+ PI_HARNESS_TOOL_PACKAGE_NAMES,
80
+ parsePiEnabledToolPackages,
81
+ } from './harness.js';
82
+ export {
83
+ AGENT_INTEGRATION_MCP_AUTH,
84
+ AGENT_INTEGRATION_MCP_ENDPOINT,
85
+ AGENT_INTEGRATION_MCP_SERVER_NAME,
86
+ AGENT_INTEGRATION_MCP_TRANSPORT,
87
+ type AgentIntegrationMcpServerConfigDto,
88
+ agentIntegrationMcpServerSchema,
89
+ type MaterializedAgentIntegrationConfigDto,
90
+ type MaterializedAgentIntegrationToolConfigDto,
91
+ type MaterializedAgentStepConfigDto,
92
+ materializedAgentIntegrationSchema,
93
+ materializedAgentIntegrationToolSchema,
94
+ materializedAgentStepConfigSchema,
95
+ } from './materialized-agent-step-config.js';
96
+ export {
97
+ getModelProviderCredentialKeys,
98
+ type ListModelProviderConfigsResponseDto,
99
+ listModelProviderConfigsResponseSchema,
100
+ type ModelProviderConfigDto,
101
+ type ModelProviderConfigResponseDto,
102
+ modelProviderConfigDtoSchema,
103
+ modelProviderConfigResponseSchema,
104
+ modelProviderCredentialKeysMatch,
105
+ type SetDefaultModelProviderBodyDto,
106
+ type SetDefaultModelProviderResponseDto,
107
+ setDefaultModelProviderBodySchema,
108
+ setDefaultModelProviderResponseSchema,
109
+ type UpdateModelProviderConfigBodyDto,
110
+ type UpdateModelProviderDefaultModelBodyDto,
111
+ updateModelProviderConfigBodySchema,
112
+ updateModelProviderDefaultModelBodySchema,
113
+ } from './model-provider-config.js';
114
+ export {
115
+ isReservedModelProviderId,
116
+ MODEL_PROVIDER_IDS,
117
+ MODEL_PROVIDER_SLUG_PATTERN,
118
+ type ModelProviderId,
119
+ type ModelProviderRef,
120
+ modelProviderRefSchema,
121
+ providerIdSchema,
122
+ SUPPORTED_MODEL_PROVIDER_IDS,
123
+ type SupportedModelProviderId,
124
+ supportedModelProviderIdSchema,
125
+ UNSUPPORTED_MODEL_PROVIDER_IDS,
126
+ } from './model-provider-id.js';
127
+ export {
128
+ type AgentRuntimeCredentialsResponseDto,
129
+ agentRuntimeCredentialsResponseSchema,
130
+ } from './runtime-config.js';
131
+ export {
132
+ type SetDefaultHarnessBodyDto,
133
+ type SetDefaultHarnessResponseDto,
134
+ setDefaultHarnessBodySchema,
135
+ setDefaultHarnessResponseSchema,
136
+ } from './workspace-defaults.js';
@@ -0,0 +1,198 @@
1
+ import {
2
+ AGENT_INTEGRATION_MCP_AUTH,
3
+ AGENT_INTEGRATION_MCP_ENDPOINT,
4
+ AGENT_INTEGRATION_MCP_SERVER_NAME,
5
+ AGENT_INTEGRATION_MCP_TRANSPORT,
6
+ materializedAgentStepConfigSchema,
7
+ } from './materialized-agent-step-config.js';
8
+
9
+ const materializedIntegration = {
10
+ connectionId: 'connection-1',
11
+ connectionSlug: 'github-main',
12
+ provider: 'github',
13
+ requiredScope: [{permission: 'issues', access: 'write'}],
14
+ tools: [
15
+ {
16
+ id: 'issue_read',
17
+ sensitivity: 'read',
18
+ sensitive: false,
19
+ requiredScope: [{permission: 'issues', access: 'read'}],
20
+ inputSchema: {type: 'object'},
21
+ methods: [
22
+ {
23
+ id: 'get',
24
+ token: 'issue_read.get',
25
+ description: 'Get issue.',
26
+ sensitivity: 'read',
27
+ sensitive: false,
28
+ requiredScope: [{permission: 'issues', access: 'read'}],
29
+ },
30
+ ],
31
+ },
32
+ {
33
+ id: 'issue_write',
34
+ sensitivity: 'write',
35
+ sensitive: false,
36
+ requiredScope: [{permission: 'issues', access: 'write'}],
37
+ inputSchema: {type: 'object'},
38
+ outputSchema: {type: 'object'},
39
+ methods: [
40
+ {
41
+ id: 'create',
42
+ token: 'issue_write.create',
43
+ description: 'Create issue.',
44
+ sensitivity: 'write',
45
+ sensitive: false,
46
+ requiredScope: [{permission: 'issues', access: 'write'}],
47
+ },
48
+ ],
49
+ },
50
+ ],
51
+ } as const;
52
+
53
+ const integrationMcpServer = {
54
+ name: AGENT_INTEGRATION_MCP_SERVER_NAME,
55
+ transport: AGENT_INTEGRATION_MCP_TRANSPORT,
56
+ endpoint: AGENT_INTEGRATION_MCP_ENDPOINT,
57
+ auth: AGENT_INTEGRATION_MCP_AUTH,
58
+ integrations: [materializedIntegration],
59
+ } as const;
60
+
61
+ describe('materializedAgentStepConfigSchema', () => {
62
+ it('accepts a materialized agent step config', () => {
63
+ const parsed = materializedAgentStepConfigSchema.parse({
64
+ harness: 'pi',
65
+ provider: 'anthropic',
66
+ model: 'claude-opus-4-8',
67
+ thinking: 'high',
68
+ tools: ['read', 'web_search'],
69
+ integrations: [materializedIntegration],
70
+ mcpServers: [integrationMcpServer],
71
+ prompt: 'Fix the failing tests.',
72
+ });
73
+
74
+ expect(parsed).toEqual({
75
+ harness: 'pi',
76
+ provider: 'anthropic',
77
+ model: 'claude-opus-4-8',
78
+ thinking: 'high',
79
+ tools: ['read', 'web_search'],
80
+ integrations: [materializedIntegration],
81
+ mcpServers: [integrationMcpServer],
82
+ prompt: 'Fix the failing tests.',
83
+ });
84
+ });
85
+
86
+ it('accepts a custom provider ref', () => {
87
+ const parsed = materializedAgentStepConfigSchema.parse({
88
+ harness: 'pi',
89
+ provider: 'local-vllm',
90
+ model: 'llama-3.1',
91
+ thinking: 'high',
92
+ prompt: 'Fix the failing tests.',
93
+ });
94
+
95
+ expect(parsed.provider).toBe('local-vllm');
96
+ });
97
+
98
+ it('defaults a missing harness for stored materialized configs', () => {
99
+ const parsed = materializedAgentStepConfigSchema.parse({
100
+ provider: 'anthropic',
101
+ model: 'claude-opus-4-8',
102
+ thinking: 'high',
103
+ prompt: 'Fix the failing tests.',
104
+ });
105
+
106
+ expect(parsed.harness).toBe('pi');
107
+ });
108
+
109
+ it('rejects missing fields and strips extra fields', () => {
110
+ const missingField = () =>
111
+ materializedAgentStepConfigSchema.parse({
112
+ harness: 'pi',
113
+ provider: 'anthropic',
114
+ model: 'claude-opus-4-8',
115
+ prompt: 'Fix the failing tests.',
116
+ });
117
+ const extraField = materializedAgentStepConfigSchema.parse({
118
+ harness: 'pi',
119
+ provider: 'anthropic',
120
+ model: 'claude-opus-4-8',
121
+ thinking: 'high',
122
+ prompt: 'Fix the failing tests.',
123
+ gate: {success: 'ok'},
124
+ });
125
+
126
+ expect(missingField).toThrow();
127
+ expect(extraField).toEqual({
128
+ harness: 'pi',
129
+ provider: 'anthropic',
130
+ model: 'claude-opus-4-8',
131
+ thinking: 'high',
132
+ prompt: 'Fix the failing tests.',
133
+ });
134
+ });
135
+
136
+ it('rejects malformed tools', () => {
137
+ const emptyTools = () =>
138
+ materializedAgentStepConfigSchema.parse({
139
+ harness: 'pi',
140
+ provider: 'anthropic',
141
+ model: 'claude-opus-4-8',
142
+ thinking: 'high',
143
+ tools: [],
144
+ prompt: 'Fix the failing tests.',
145
+ });
146
+
147
+ expect(emptyTools).toThrow();
148
+ });
149
+
150
+ it('rejects malformed integrations', () => {
151
+ const emptyTools = () =>
152
+ materializedAgentStepConfigSchema.parse({
153
+ harness: 'pi',
154
+ provider: 'anthropic',
155
+ model: 'claude-opus-4-8',
156
+ thinking: 'high',
157
+ integrations: [
158
+ {
159
+ connectionId: 'connection-1',
160
+ connectionSlug: 'github-main',
161
+ provider: 'github',
162
+ requiredScope: [],
163
+ tools: [],
164
+ },
165
+ ],
166
+ prompt: 'Fix the failing tests.',
167
+ });
168
+
169
+ expect(emptyTools).toThrow();
170
+ });
171
+
172
+ it.each([
173
+ ['empty integrations', {...integrationMcpServer, integrations: []}],
174
+ ['wrong auth', {...integrationMcpServer, auth: 'provider_token'}],
175
+ ['wrong transport', {...integrationMcpServer, transport: 'stdio'}],
176
+ ['missing endpoint', omit(integrationMcpServer, 'endpoint')],
177
+ ['missing name', omit(integrationMcpServer, 'name')],
178
+ ])('rejects malformed integration MCP server config for %s', (_caseName, mcpServer) => {
179
+ const parse = () =>
180
+ materializedAgentStepConfigSchema.parse({
181
+ harness: 'pi',
182
+ provider: 'anthropic',
183
+ model: 'claude-opus-4-8',
184
+ thinking: 'high',
185
+ integrations: [materializedIntegration],
186
+ mcpServers: [mcpServer],
187
+ prompt: 'Fix the failing tests.',
188
+ });
189
+
190
+ expect(parse).toThrow();
191
+ });
192
+ });
193
+
194
+ function omit<T extends object, K extends keyof T>(object: T, key: K): Omit<T, K> {
195
+ const copy = {...object};
196
+ delete copy[key];
197
+ return copy;
198
+ }
@@ -0,0 +1,69 @@
1
+ import {agentThinkingSchema, DEFAULT_HARNESS, harnessSchema} from '@shipfox/workflow-document';
2
+ import {z} from 'zod';
3
+ import {modelProviderRefSchema} from './model-provider-id.js';
4
+
5
+ const agentToolSensitivitySchema = z.enum(['read', 'write']);
6
+ const agentToolJsonSchema = z.record(z.string(), z.unknown());
7
+ const agentToolRequiredScopeSchema = z.array(z.unknown());
8
+
9
+ export const materializedAgentIntegrationToolMethodSchema = z.strictObject({
10
+ id: z.string().min(1),
11
+ token: z.string().min(1),
12
+ description: z.string().min(1).optional(),
13
+ sensitivity: agentToolSensitivitySchema,
14
+ sensitive: z.boolean(),
15
+ requiredScope: agentToolRequiredScopeSchema,
16
+ });
17
+
18
+ export const materializedAgentIntegrationToolSchema = z.strictObject({
19
+ id: z.string().min(1),
20
+ sensitivity: agentToolSensitivitySchema,
21
+ sensitive: z.boolean(),
22
+ requiredScope: agentToolRequiredScopeSchema,
23
+ inputSchema: agentToolJsonSchema,
24
+ outputSchema: agentToolJsonSchema.optional(),
25
+ methods: z.array(materializedAgentIntegrationToolMethodSchema).min(1).optional(),
26
+ });
27
+
28
+ export const materializedAgentIntegrationSchema = z.strictObject({
29
+ connectionId: z.string().min(1),
30
+ connectionSlug: z.string().min(1),
31
+ provider: z.string().min(1),
32
+ requiredScope: agentToolRequiredScopeSchema,
33
+ tools: z.array(materializedAgentIntegrationToolSchema).min(1),
34
+ });
35
+
36
+ export const AGENT_INTEGRATION_MCP_SERVER_NAME = 'shipfox_integration_tools';
37
+ export const AGENT_INTEGRATION_MCP_ENDPOINT = '/runs/jobs/current/integration-tools/mcp';
38
+ export const AGENT_INTEGRATION_MCP_TRANSPORT = 'http';
39
+ export const AGENT_INTEGRATION_MCP_AUTH = 'lease_token';
40
+
41
+ export const agentIntegrationMcpServerSchema = z.strictObject({
42
+ name: z.literal(AGENT_INTEGRATION_MCP_SERVER_NAME),
43
+ transport: z.literal(AGENT_INTEGRATION_MCP_TRANSPORT),
44
+ endpoint: z.literal(AGENT_INTEGRATION_MCP_ENDPOINT),
45
+ auth: z.literal(AGENT_INTEGRATION_MCP_AUTH),
46
+ integrations: z.array(materializedAgentIntegrationSchema).min(1),
47
+ });
48
+
49
+ export const materializedAgentStepConfigSchema = z
50
+ .object({
51
+ harness: harnessSchema.default(DEFAULT_HARNESS),
52
+ provider: modelProviderRefSchema,
53
+ model: z.string().min(1),
54
+ thinking: agentThinkingSchema,
55
+ tools: z.array(z.string().min(1)).min(1).optional(),
56
+ integrations: z.array(materializedAgentIntegrationSchema).min(1).optional(),
57
+ mcpServers: z.array(agentIntegrationMcpServerSchema).length(1).optional(),
58
+ prompt: z.string(),
59
+ })
60
+ .strip();
61
+
62
+ export type MaterializedAgentStepConfigDto = z.infer<typeof materializedAgentStepConfigSchema>;
63
+ export type MaterializedAgentIntegrationConfigDto = z.infer<
64
+ typeof materializedAgentIntegrationSchema
65
+ >;
66
+ export type MaterializedAgentIntegrationToolConfigDto = z.infer<
67
+ typeof materializedAgentIntegrationToolSchema
68
+ >;
69
+ export type AgentIntegrationMcpServerConfigDto = z.infer<typeof agentIntegrationMcpServerSchema>;