@shipfox/api-agent 13.1.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/core/resolve-runtime-credentials.d.ts.map +1 -1
- package/dist/core/resolve-runtime-credentials.js +4 -3
- package/dist/core/resolve-runtime-credentials.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/config.test.ts +31 -0
- package/src/config.ts +6 -0
- package/src/core/resolve-runtime-credentials.test.ts +122 -2
- package/src/core/resolve-runtime-credentials.ts +2 -1
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-agent",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "14.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"drizzle-orm": "^0.45.2",
|
|
40
40
|
"zod": "^4.4.3",
|
|
41
41
|
"@shipfox/node-error-monitoring": "0.3.0",
|
|
42
|
-
"@shipfox/api-agent-dto": "
|
|
42
|
+
"@shipfox/api-agent-dto": "14.0.0",
|
|
43
43
|
"@shipfox/api-auth-context": "12.2.0",
|
|
44
44
|
"@shipfox/api-secrets-dto": "12.0.0",
|
|
45
45
|
"@shipfox/inter-module": "0.2.3",
|
package/src/config.test.ts
CHANGED
|
@@ -155,6 +155,23 @@ describe('agent config', () => {
|
|
|
155
155
|
}),
|
|
156
156
|
'model API is invalid',
|
|
157
157
|
],
|
|
158
|
+
...(
|
|
159
|
+
[
|
|
160
|
+
['null context window', {context_window: null}],
|
|
161
|
+
['zero max output tokens', {max_output_tokens: 0}],
|
|
162
|
+
['fractional context window', {context_window: 1.5}],
|
|
163
|
+
['string context window', {context_window: '128000'}],
|
|
164
|
+
['non-boolean reasoning', {reasoning: 'true'}],
|
|
165
|
+
['non-boolean input image', {input_image: 1}],
|
|
166
|
+
] as const
|
|
167
|
+
).map(
|
|
168
|
+
([name, metadata]) =>
|
|
169
|
+
[
|
|
170
|
+
`invalid ${name}`,
|
|
171
|
+
managedProviderWithModelMetadata(metadata),
|
|
172
|
+
'model metadata is invalid',
|
|
173
|
+
] as const,
|
|
174
|
+
),
|
|
158
175
|
[
|
|
159
176
|
'unregistered default model',
|
|
160
177
|
managedProvider({defaultModel: 'missing-model'}),
|
|
@@ -277,3 +294,17 @@ function managedProvider(overrides: Partial<ManagedModelProvider> = {}): Managed
|
|
|
277
294
|
...overrides,
|
|
278
295
|
};
|
|
279
296
|
}
|
|
297
|
+
|
|
298
|
+
function managedProviderWithModelMetadata(metadata: object): ManagedModelProvider {
|
|
299
|
+
return managedProvider({
|
|
300
|
+
models: [
|
|
301
|
+
{
|
|
302
|
+
id: 'managed-model',
|
|
303
|
+
label: 'Managed model',
|
|
304
|
+
api: 'anthropic-messages',
|
|
305
|
+
...metadata,
|
|
306
|
+
} as ManagedModelEntry,
|
|
307
|
+
],
|
|
308
|
+
defaultModel: 'managed-model',
|
|
309
|
+
});
|
|
310
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isReservedModelProviderId,
|
|
5
5
|
type ManagedModelProvider,
|
|
6
6
|
managedModelApiSchema,
|
|
7
|
+
managedModelMetadataSchema,
|
|
7
8
|
modelProviderRefSchema,
|
|
8
9
|
SUPPORTED_MODEL_PROVIDER_IDS,
|
|
9
10
|
type WorkspaceProvidersPolicy,
|
|
@@ -143,6 +144,11 @@ function assertManagedProvider(provider: ManagedModelProvider): void {
|
|
|
143
144
|
if (!managedModelApiSchema.safeParse(model.api).success) {
|
|
144
145
|
throw new Error(`Managed model provider model API is invalid: ${provider.id}/${model.id}.`);
|
|
145
146
|
}
|
|
147
|
+
if (!managedModelMetadataSchema.safeParse(model).success) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Managed model provider model metadata is invalid: ${provider.id}/${model.id}.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
if (!modelIds.has(provider.defaultModel)) {
|
|
@@ -78,12 +78,115 @@ describe('resolveRuntimeCredentials', () => {
|
|
|
78
78
|
base_url: 'https://gateway.example.test',
|
|
79
79
|
headers: [],
|
|
80
80
|
secret_header_names: [],
|
|
81
|
-
models: [
|
|
81
|
+
models: [
|
|
82
|
+
{
|
|
83
|
+
id: 'responses-model',
|
|
84
|
+
label: 'Responses model',
|
|
85
|
+
context_window: 1_000_000,
|
|
86
|
+
max_output_tokens: 65_536,
|
|
87
|
+
reasoning: true,
|
|
88
|
+
input_image: true,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
82
91
|
requires_api_key: true,
|
|
83
92
|
},
|
|
84
93
|
});
|
|
85
94
|
});
|
|
86
95
|
|
|
96
|
+
it('omits optional model metadata when the managed model entry does not carry it', async () => {
|
|
97
|
+
const resolveCredentials = vi.fn<ManagedModelProvider['resolveCredentials']>();
|
|
98
|
+
resolveCredentials.mockResolvedValue({
|
|
99
|
+
api: 'openai-completions',
|
|
100
|
+
baseUrl: 'https://gateway.example.test',
|
|
101
|
+
credentials: {api_key: 'managed-token'},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const result = await resolveRuntimeCredentials(
|
|
105
|
+
{
|
|
106
|
+
workspaceId,
|
|
107
|
+
runId: crypto.randomUUID(),
|
|
108
|
+
stepAttemptId: crypto.randomUUID(),
|
|
109
|
+
harness: 'pi',
|
|
110
|
+
provider: 'shipfox',
|
|
111
|
+
model: 'plain-model',
|
|
112
|
+
thinking: 'high',
|
|
113
|
+
},
|
|
114
|
+
{managedProvider: managedProvider(resolveCredentials)},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(result).toEqual({
|
|
118
|
+
harness: 'pi',
|
|
119
|
+
provider_id: 'shipfox',
|
|
120
|
+
model: 'plain-model',
|
|
121
|
+
thinking: 'high',
|
|
122
|
+
credentials: {api_key: 'managed-token'},
|
|
123
|
+
custom_provider: {
|
|
124
|
+
api: 'openai-completions',
|
|
125
|
+
base_url: 'https://gateway.example.test',
|
|
126
|
+
headers: [],
|
|
127
|
+
secret_header_names: [],
|
|
128
|
+
models: [{id: 'plain-model', label: 'Plain model'}],
|
|
129
|
+
requires_api_key: true,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('forwards partial managed model metadata, including false values', async () => {
|
|
135
|
+
const resolveCredentials = vi.fn<ManagedModelProvider['resolveCredentials']>();
|
|
136
|
+
resolveCredentials.mockResolvedValue({
|
|
137
|
+
api: 'openai-completions',
|
|
138
|
+
baseUrl: 'https://gateway.example.test',
|
|
139
|
+
credentials: {api_key: 'managed-token'},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const result = await resolveRuntimeCredentials(
|
|
143
|
+
{
|
|
144
|
+
workspaceId,
|
|
145
|
+
runId: crypto.randomUUID(),
|
|
146
|
+
stepAttemptId: crypto.randomUUID(),
|
|
147
|
+
harness: 'pi',
|
|
148
|
+
provider: 'shipfox',
|
|
149
|
+
model: 'partial-model',
|
|
150
|
+
thinking: 'high',
|
|
151
|
+
},
|
|
152
|
+
{managedProvider: managedProvider(resolveCredentials)},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(result.custom_provider?.models).toEqual([
|
|
156
|
+
{
|
|
157
|
+
id: 'partial-model',
|
|
158
|
+
label: 'Partial model',
|
|
159
|
+
max_output_tokens: 8_192,
|
|
160
|
+
reasoning: false,
|
|
161
|
+
input_image: false,
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('falls back to an unregistered managed model descriptor', async () => {
|
|
167
|
+
const resolveCredentials = vi.fn<ManagedModelProvider['resolveCredentials']>();
|
|
168
|
+
resolveCredentials.mockResolvedValue({
|
|
169
|
+
api: 'openai-completions',
|
|
170
|
+
baseUrl: 'https://gateway.example.test',
|
|
171
|
+
credentials: {api_key: 'managed-token'},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = await resolveRuntimeCredentials(
|
|
175
|
+
{
|
|
176
|
+
workspaceId,
|
|
177
|
+
runId: crypto.randomUUID(),
|
|
178
|
+
stepAttemptId: crypto.randomUUID(),
|
|
179
|
+
harness: 'pi',
|
|
180
|
+
provider: 'shipfox',
|
|
181
|
+
model: 'missing-model',
|
|
182
|
+
thinking: 'high',
|
|
183
|
+
},
|
|
184
|
+
{managedProvider: managedProvider(resolveCredentials)},
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(result.custom_provider?.models).toEqual([{id: 'missing-model', label: 'missing-model'}]);
|
|
188
|
+
});
|
|
189
|
+
|
|
87
190
|
it('resolves managed provider credentials into the Claude per-step contract', async () => {
|
|
88
191
|
const resolveCredentials = vi.fn<ManagedModelProvider['resolveCredentials']>();
|
|
89
192
|
resolveCredentials.mockResolvedValue({
|
|
@@ -476,7 +579,24 @@ function managedProvider(
|
|
|
476
579
|
label: 'Shipfox',
|
|
477
580
|
models: [
|
|
478
581
|
{id: 'claude-model', label: 'Claude model', api: 'anthropic-messages'},
|
|
479
|
-
{
|
|
582
|
+
{
|
|
583
|
+
id: 'responses-model',
|
|
584
|
+
label: 'Responses model',
|
|
585
|
+
api: 'openai-responses',
|
|
586
|
+
context_window: 1_000_000,
|
|
587
|
+
max_output_tokens: 65_536,
|
|
588
|
+
reasoning: true,
|
|
589
|
+
input_image: true,
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
id: 'partial-model',
|
|
593
|
+
label: 'Partial model',
|
|
594
|
+
api: 'openai-completions',
|
|
595
|
+
max_output_tokens: 8_192,
|
|
596
|
+
reasoning: false,
|
|
597
|
+
input_image: false,
|
|
598
|
+
},
|
|
599
|
+
{id: 'plain-model', label: 'Plain model', api: 'openai-completions'},
|
|
480
600
|
],
|
|
481
601
|
defaultModel: 'responses-model',
|
|
482
602
|
resolveCredentials,
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
SupportedModelProviderId,
|
|
9
9
|
WorkspaceProvidersPolicy,
|
|
10
10
|
} from '@shipfox/api-agent-dto';
|
|
11
|
+
import {toCustomAgentModelDto} from '@shipfox/api-agent-dto';
|
|
11
12
|
import {secretsInterModuleContract} from '@shipfox/api-secrets-dto/inter-module';
|
|
12
13
|
import {isInterModuleKnownError} from '@shipfox/inter-module';
|
|
13
14
|
import {config, workspaceProvidersPolicy} from '#config.js';
|
|
@@ -198,7 +199,7 @@ function toResponse(
|
|
|
198
199
|
|
|
199
200
|
if (managed !== undefined) {
|
|
200
201
|
const model = managed.provider.models.find((candidate) => candidate.id === params.model);
|
|
201
|
-
const modelDescriptor = {id: params.model, label:
|
|
202
|
+
const modelDescriptor = toCustomAgentModelDto(model ?? {id: params.model, label: params.model});
|
|
202
203
|
|
|
203
204
|
if (params.harness === 'pi') {
|
|
204
205
|
response.custom_provider = {
|