@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,441 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import type {AgentThinking, SupportedModelProviderId} from '@shipfox/api-agent-dto';
|
|
3
|
+
import {
|
|
4
|
+
getAgentWorkspaceSettings,
|
|
5
|
+
setDefaultHarness,
|
|
6
|
+
setDefaultModelProvider,
|
|
7
|
+
type UpsertModelProviderConfigParams,
|
|
8
|
+
upsertModelProviderConfig,
|
|
9
|
+
} from '#db/index.js';
|
|
10
|
+
import {
|
|
11
|
+
InvalidAgentModelError,
|
|
12
|
+
UnsupportedHarnessProviderError,
|
|
13
|
+
UnsupportedHarnessThinkingError,
|
|
14
|
+
UnsupportedModelProviderError,
|
|
15
|
+
} from './errors.js';
|
|
16
|
+
import * as harnessCatalog from './harness/index.js';
|
|
17
|
+
import {catalogDefaultAgentResolver, resolveAgentConfig} from './resolve-agent-config.js';
|
|
18
|
+
import {createWorkspaceAgentDefaultsResolver} from './workspace-agent-defaults-resolver.js';
|
|
19
|
+
|
|
20
|
+
describe('resolveAgentConfig', () => {
|
|
21
|
+
test('resolves model provider from explicit step, workspace, instance, then catalog default', () => {
|
|
22
|
+
const workspaceProviderConfigs = new Map([
|
|
23
|
+
['openai' as const, {defaultModel: 'gpt-5.5-pro', defaultThinking: 'medium' as const}],
|
|
24
|
+
[
|
|
25
|
+
'google' as const,
|
|
26
|
+
{defaultModel: 'gemini-3.1-pro-preview', defaultThinking: 'low' as const},
|
|
27
|
+
],
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const explicit = resolveAgentConfig(
|
|
31
|
+
{provider: 'google'},
|
|
32
|
+
{
|
|
33
|
+
workspaceDefaultProviderId: 'openai',
|
|
34
|
+
workspaceProviderConfigs,
|
|
35
|
+
instanceDefaultProvider: 'anthropic',
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
const workspace = resolveAgentConfig(
|
|
39
|
+
{},
|
|
40
|
+
{workspaceDefaultProviderId: 'openai', workspaceProviderConfigs},
|
|
41
|
+
);
|
|
42
|
+
const instance = resolveAgentConfig({}, {instanceDefaultProvider: 'anthropic'});
|
|
43
|
+
const catalog = resolveAgentConfig({});
|
|
44
|
+
|
|
45
|
+
expect(explicit.provider).toBe('google');
|
|
46
|
+
expect(workspace.provider).toBe('openai');
|
|
47
|
+
expect(instance.provider).toBe('anthropic');
|
|
48
|
+
expect(catalog.provider).toBe('anthropic');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('resolves model from explicit step, workspace, instance match, then catalog default', () => {
|
|
52
|
+
const workspaceProviderConfigs = new Map([
|
|
53
|
+
['openai' as const, {defaultModel: 'gpt-5.5-pro', defaultThinking: 'medium' as const}],
|
|
54
|
+
['anthropic' as const, {defaultModel: null, defaultThinking: 'low' as const}],
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const explicit = resolveAgentConfig(
|
|
58
|
+
{provider: 'openai', model: 'gpt-5.5-pro'},
|
|
59
|
+
{workspaceProviderConfigs},
|
|
60
|
+
);
|
|
61
|
+
const workspace = resolveAgentConfig({provider: 'openai'}, {workspaceProviderConfigs});
|
|
62
|
+
const instance = resolveAgentConfig(
|
|
63
|
+
{},
|
|
64
|
+
{
|
|
65
|
+
instanceDefaultProvider: 'anthropic',
|
|
66
|
+
instanceDefaultModel: 'claude-opus-4-8',
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
const workspaceLatest = resolveAgentConfig({provider: 'anthropic'}, {workspaceProviderConfigs});
|
|
70
|
+
const catalog = resolveAgentConfig({provider: 'deepseek'});
|
|
71
|
+
|
|
72
|
+
expect(explicit.model).toBe('gpt-5.5-pro');
|
|
73
|
+
expect(workspace.model).toBe('gpt-5.5-pro');
|
|
74
|
+
expect(instance.model).toBe('claude-opus-4-8');
|
|
75
|
+
expect(workspaceLatest.model).toBe('claude-opus-4-8');
|
|
76
|
+
expect(catalog.model).toBe('deepseek-v4-pro');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('resolves custom provider models from workspace configs', () => {
|
|
80
|
+
const workspaceProviderConfigs = new Map([
|
|
81
|
+
[
|
|
82
|
+
'local-vllm',
|
|
83
|
+
{
|
|
84
|
+
kind: 'custom' as const,
|
|
85
|
+
defaultModel: null,
|
|
86
|
+
defaultThinking: 'medium' as const,
|
|
87
|
+
models: [{id: 'llama-3.1', label: 'Llama 3.1'}],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const resolved = resolveAgentConfig(
|
|
93
|
+
{provider: 'local-vllm'},
|
|
94
|
+
{workspaceProviderConfigs, workspaceDefaultProviderId: 'local-vllm'},
|
|
95
|
+
);
|
|
96
|
+
const invalidModel = () =>
|
|
97
|
+
resolveAgentConfig(
|
|
98
|
+
{provider: 'local-vllm', model: 'missing-model'},
|
|
99
|
+
{workspaceProviderConfigs},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(resolved).toEqual({
|
|
103
|
+
harness: 'pi',
|
|
104
|
+
provider: 'local-vllm',
|
|
105
|
+
model: 'llama-3.1',
|
|
106
|
+
thinking: 'medium',
|
|
107
|
+
});
|
|
108
|
+
expect(invalidModel).toThrow(InvalidAgentModelError);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('uses instance model and thinking only for the resolved instance model provider', () => {
|
|
112
|
+
const resolved = resolveAgentConfig(
|
|
113
|
+
{provider: 'openai'},
|
|
114
|
+
{
|
|
115
|
+
instanceDefaultProvider: 'anthropic',
|
|
116
|
+
instanceDefaultModel: 'claude-opus-4-8',
|
|
117
|
+
instanceDefaultThinking: 'low',
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(resolved).toEqual({
|
|
122
|
+
harness: 'pi',
|
|
123
|
+
provider: 'openai',
|
|
124
|
+
model: 'gpt-5.5-pro',
|
|
125
|
+
thinking: 'xhigh',
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('resolves harness from explicit step, then default', () => {
|
|
130
|
+
const explicit = resolveAgentConfig({harness: 'claude'});
|
|
131
|
+
const workspace = resolveAgentConfig({}, {workspaceDefaultHarnessId: 'claude'});
|
|
132
|
+
const fallback = resolveAgentConfig({});
|
|
133
|
+
|
|
134
|
+
expect(explicit.harness).toBe('claude');
|
|
135
|
+
expect(workspace.harness).toBe('claude');
|
|
136
|
+
expect(fallback.harness).toBe('pi');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('resolves thinking from explicit step, workspace, instance match, then default', () => {
|
|
140
|
+
const workspaceProviderConfigs = new Map([
|
|
141
|
+
['openai' as const, {defaultModel: 'gpt-5.5-pro', defaultThinking: 'medium' as const}],
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
const explicit = resolveAgentConfig(
|
|
145
|
+
{provider: 'openai', thinking: 'low'},
|
|
146
|
+
{workspaceProviderConfigs},
|
|
147
|
+
);
|
|
148
|
+
const workspace = resolveAgentConfig({provider: 'openai'}, {workspaceProviderConfigs});
|
|
149
|
+
const instance = resolveAgentConfig(
|
|
150
|
+
{},
|
|
151
|
+
{instanceDefaultProvider: 'anthropic', instanceDefaultThinking: 'medium'},
|
|
152
|
+
);
|
|
153
|
+
const fallback = resolveAgentConfig({provider: 'deepseek'});
|
|
154
|
+
|
|
155
|
+
expect(explicit.thinking).toBe('low');
|
|
156
|
+
expect(workspace.thinking).toBe('medium');
|
|
157
|
+
expect(instance.thinking).toBe('medium');
|
|
158
|
+
expect(fallback.thinking).toBe('xhigh');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('throws for unsupported providers and unavailable models', () => {
|
|
162
|
+
expect(() => resolveAgentConfig({provider: 'amazon-bedrock'})).toThrow(
|
|
163
|
+
UnsupportedModelProviderError,
|
|
164
|
+
);
|
|
165
|
+
expect(() => resolveAgentConfig({provider: 'anthropic', model: 'not-a-model'})).toThrow(
|
|
166
|
+
InvalidAgentModelError,
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('rejects explicit provider values incompatible with the harness', () => {
|
|
171
|
+
const resolve = () => resolveAgentConfig({harness: 'claude', provider: 'openai'});
|
|
172
|
+
|
|
173
|
+
expect(resolve).toThrow(UnsupportedHarnessProviderError);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('skips incompatible default providers for the selected harness', () => {
|
|
177
|
+
const workspaceProviderConfigs = new Map([
|
|
178
|
+
['openai' as const, {defaultModel: 'gpt-5.5-pro', defaultThinking: 'medium' as const}],
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
const resolved = resolveAgentConfig(
|
|
182
|
+
{harness: 'claude'},
|
|
183
|
+
{
|
|
184
|
+
workspaceDefaultProviderId: 'openai',
|
|
185
|
+
workspaceProviderConfigs,
|
|
186
|
+
instanceDefaultProvider: 'deepseek',
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(resolved.provider).toBe('anthropic');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('keeps custom providers pi-only', () => {
|
|
194
|
+
const workspaceProviderConfigs = new Map([
|
|
195
|
+
[
|
|
196
|
+
'local-vllm',
|
|
197
|
+
{
|
|
198
|
+
kind: 'custom' as const,
|
|
199
|
+
defaultModel: null,
|
|
200
|
+
defaultThinking: 'medium' as const,
|
|
201
|
+
models: [{id: 'llama-3.1', label: 'Llama 3.1'}],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
const pi = resolveAgentConfig({provider: 'local-vllm'}, {workspaceProviderConfigs});
|
|
207
|
+
const claude = () =>
|
|
208
|
+
resolveAgentConfig({harness: 'claude', provider: 'local-vllm'}, {workspaceProviderConfigs});
|
|
209
|
+
|
|
210
|
+
expect(pi.provider).toBe('local-vllm');
|
|
211
|
+
expect(claude).toThrow(UnsupportedHarnessProviderError);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('rejects explicit models outside the harness catalog', () => {
|
|
215
|
+
const resolve = () =>
|
|
216
|
+
resolveAgentConfig({harness: 'claude', provider: 'anthropic', model: 'gpt-5.5-pro'});
|
|
217
|
+
|
|
218
|
+
expect(resolve).toThrow(InvalidAgentModelError);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('skips default models outside the harness catalog', () => {
|
|
222
|
+
const resolved = resolveAgentConfig(
|
|
223
|
+
{harness: 'claude', provider: 'anthropic'},
|
|
224
|
+
{
|
|
225
|
+
instanceDefaultProvider: 'anthropic',
|
|
226
|
+
instanceDefaultModel: 'not-a-model',
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
expect(resolved.model).toBe('claude-opus-4-8');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('does not evaluate catalog model fallback before a valid workspace default model', () => {
|
|
234
|
+
vi.spyOn(harnessCatalog, 'listHarnessProviderModels').mockImplementation(
|
|
235
|
+
(harness, provider) => {
|
|
236
|
+
if (new Error('capture catalog lookup stack').stack?.includes('catalogDefaultModel')) {
|
|
237
|
+
throw new InvalidAgentModelError(harness, provider, '');
|
|
238
|
+
}
|
|
239
|
+
return [{id: 'claude-opus-4-8', label: 'Claude Opus 4.8'}];
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
const workspaceProviderConfigs = new Map([
|
|
243
|
+
['anthropic' as const, {defaultModel: 'claude-opus-4-8', defaultThinking: 'medium' as const}],
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
const resolved = resolveAgentConfig({provider: 'anthropic'}, {workspaceProviderConfigs});
|
|
247
|
+
|
|
248
|
+
expect(resolved.model).toBe('claude-opus-4-8');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('validates thinking against the selected harness', () => {
|
|
252
|
+
const claudeOff = () => resolveAgentConfig({harness: 'claude', thinking: 'off'});
|
|
253
|
+
const piMax = () => resolveAgentConfig({harness: 'pi', thinking: 'max'});
|
|
254
|
+
|
|
255
|
+
expect(claudeOff).toThrow(UnsupportedHarnessThinkingError);
|
|
256
|
+
expect(piMax).toThrow(UnsupportedHarnessThinkingError);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('ignores stale provider thinking defaults outside the selected harness', () => {
|
|
260
|
+
const workspaceProviderConfigs = new Map([
|
|
261
|
+
['anthropic' as const, {defaultModel: 'claude-opus-4-8', defaultThinking: 'off' as const}],
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
const resolved = resolveAgentConfig(
|
|
265
|
+
{harness: 'claude', provider: 'anthropic'},
|
|
266
|
+
{workspaceProviderConfigs},
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
expect(resolved.thinking).toBe('xhigh');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('falls through to the instance default when the workspace default model provider is null', () => {
|
|
273
|
+
const resolved = resolveAgentConfig(
|
|
274
|
+
{},
|
|
275
|
+
{workspaceDefaultProviderId: null, instanceDefaultProvider: 'anthropic'},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(resolved.provider).toBe('anthropic');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('skips a stored workspace default model provider that is no longer supported', () => {
|
|
282
|
+
const resolved = resolveAgentConfig(
|
|
283
|
+
{},
|
|
284
|
+
{workspaceDefaultProviderId: 'amazon-bedrock' as SupportedModelProviderId},
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
expect(resolved.provider).toBe('anthropic');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('skips an unknown instance default model', () => {
|
|
291
|
+
const resolved = resolveAgentConfig(
|
|
292
|
+
{},
|
|
293
|
+
{
|
|
294
|
+
instanceDefaultProvider: 'anthropic',
|
|
295
|
+
instanceDefaultModel: 'not-a-model',
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(resolved.model).toBe('claude-opus-4-8');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('catalogDefaultAgentResolver uses catalog-only defaults', () => {
|
|
303
|
+
const resolved = catalogDefaultAgentResolver({provider: 'openai'});
|
|
304
|
+
|
|
305
|
+
expect(resolved).toEqual({
|
|
306
|
+
harness: 'pi',
|
|
307
|
+
provider: 'openai',
|
|
308
|
+
model: 'gpt-5.5-pro',
|
|
309
|
+
thinking: 'xhigh',
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('createWorkspaceAgentDefaultsResolver', () => {
|
|
315
|
+
let workspaceId: string;
|
|
316
|
+
|
|
317
|
+
beforeEach(() => {
|
|
318
|
+
workspaceId = crypto.randomUUID();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('preloads workspace settings and model provider configs for resolution', async () => {
|
|
322
|
+
await upsertModelProviderConfig(
|
|
323
|
+
createModelProviderConfigParams({
|
|
324
|
+
workspaceId,
|
|
325
|
+
providerId: 'openai',
|
|
326
|
+
defaultModel: 'gpt-5.5-pro',
|
|
327
|
+
defaultThinking: 'medium',
|
|
328
|
+
}),
|
|
329
|
+
);
|
|
330
|
+
await setDefaultModelProvider({workspaceId, providerId: 'openai'});
|
|
331
|
+
await setDefaultHarness({workspaceId, harnessId: 'claude'});
|
|
332
|
+
|
|
333
|
+
const resolver = await createWorkspaceAgentDefaultsResolver(workspaceId);
|
|
334
|
+
const resolved = resolver({});
|
|
335
|
+
const settings = await getAgentWorkspaceSettings(workspaceId);
|
|
336
|
+
|
|
337
|
+
expect(settings?.defaultProviderId).toBe('openai');
|
|
338
|
+
expect(settings?.defaultHarnessId).toBe('claude');
|
|
339
|
+
expect(resolved).toEqual({
|
|
340
|
+
harness: 'claude',
|
|
341
|
+
provider: 'anthropic',
|
|
342
|
+
model: 'claude-opus-4-8',
|
|
343
|
+
thinking: 'xhigh',
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test('falls back to catalog defaults when no workspace settings exist', async () => {
|
|
348
|
+
const resolver = await createWorkspaceAgentDefaultsResolver(workspaceId);
|
|
349
|
+
|
|
350
|
+
const resolved = resolver({});
|
|
351
|
+
|
|
352
|
+
expect(resolved).toEqual({
|
|
353
|
+
harness: 'pi',
|
|
354
|
+
provider: 'anthropic',
|
|
355
|
+
model: 'claude-opus-4-8',
|
|
356
|
+
thinking: 'xhigh',
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('uses workspace model provider config defaults when settings row does not exist', async () => {
|
|
361
|
+
await upsertModelProviderConfig(
|
|
362
|
+
createModelProviderConfigParams({
|
|
363
|
+
workspaceId,
|
|
364
|
+
providerId: 'openai',
|
|
365
|
+
defaultModel: 'gpt-5.5-pro',
|
|
366
|
+
defaultThinking: 'medium',
|
|
367
|
+
}),
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const resolver = await createWorkspaceAgentDefaultsResolver(workspaceId);
|
|
371
|
+
const resolved = resolver({provider: 'openai'});
|
|
372
|
+
|
|
373
|
+
expect(resolved).toEqual({
|
|
374
|
+
harness: 'pi',
|
|
375
|
+
provider: 'openai',
|
|
376
|
+
model: 'gpt-5.5-pro',
|
|
377
|
+
thinking: 'medium',
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test('uses catalog model when the workspace model provider config keeps latest selected', async () => {
|
|
382
|
+
await upsertModelProviderConfig(
|
|
383
|
+
createModelProviderConfigParams({
|
|
384
|
+
workspaceId,
|
|
385
|
+
providerId: 'openai',
|
|
386
|
+
defaultModel: null,
|
|
387
|
+
defaultThinking: 'medium',
|
|
388
|
+
}),
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
const resolver = await createWorkspaceAgentDefaultsResolver(workspaceId);
|
|
392
|
+
const resolved = resolver({provider: 'openai'});
|
|
393
|
+
|
|
394
|
+
expect(resolved).toEqual({
|
|
395
|
+
harness: 'pi',
|
|
396
|
+
provider: 'openai',
|
|
397
|
+
model: 'gpt-5.5-pro',
|
|
398
|
+
thinking: 'medium',
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('uses custom workspace defaults without filtering the provider config', async () => {
|
|
403
|
+
await upsertModelProviderConfig({
|
|
404
|
+
workspaceId,
|
|
405
|
+
providerId: 'local-vllm',
|
|
406
|
+
kind: 'custom',
|
|
407
|
+
displayName: 'Local vLLM',
|
|
408
|
+
api: 'openai-responses',
|
|
409
|
+
baseUrl: 'http://127.0.0.1:11434/v1',
|
|
410
|
+
headers: [],
|
|
411
|
+
models: [{id: 'llama-3.1', label: 'Llama 3.1'}],
|
|
412
|
+
defaultModel: null,
|
|
413
|
+
defaultThinking: 'low',
|
|
414
|
+
});
|
|
415
|
+
await setDefaultModelProvider({workspaceId, providerId: 'local-vllm'});
|
|
416
|
+
|
|
417
|
+
const resolver = await createWorkspaceAgentDefaultsResolver(workspaceId);
|
|
418
|
+
const resolved = resolver({});
|
|
419
|
+
|
|
420
|
+
expect(resolved).toEqual({
|
|
421
|
+
harness: 'pi',
|
|
422
|
+
provider: 'local-vllm',
|
|
423
|
+
model: 'llama-3.1',
|
|
424
|
+
thinking: 'low',
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
function createModelProviderConfigParams(params: {
|
|
430
|
+
workspaceId: string;
|
|
431
|
+
providerId: SupportedModelProviderId;
|
|
432
|
+
defaultModel: string | null;
|
|
433
|
+
defaultThinking: AgentThinking;
|
|
434
|
+
}): UpsertModelProviderConfigParams {
|
|
435
|
+
return {
|
|
436
|
+
workspaceId: params.workspaceId,
|
|
437
|
+
providerId: params.providerId,
|
|
438
|
+
defaultModel: params.defaultModel,
|
|
439
|
+
defaultThinking: params.defaultThinking,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AgentThinking,
|
|
3
|
+
agentThinkingByHarness,
|
|
4
|
+
type CustomAgentModelDto,
|
|
5
|
+
DEFAULT_HARNESS,
|
|
6
|
+
getHarnessDescriptor,
|
|
7
|
+
getModelProviderEntry,
|
|
8
|
+
type Harness,
|
|
9
|
+
type ModelProviderRef,
|
|
10
|
+
type SupportedModelProviderId,
|
|
11
|
+
} from '@shipfox/api-agent-dto';
|
|
12
|
+
import {
|
|
13
|
+
InvalidAgentModelError,
|
|
14
|
+
UnsupportedHarnessProviderError,
|
|
15
|
+
UnsupportedHarnessThinkingError,
|
|
16
|
+
UnsupportedModelProviderError,
|
|
17
|
+
} from './errors.js';
|
|
18
|
+
import {listHarnessProviderModels} from './harness/index.js';
|
|
19
|
+
|
|
20
|
+
export interface ContextualAgentConfig {
|
|
21
|
+
readonly harness?: Harness | undefined;
|
|
22
|
+
readonly provider?: string | undefined;
|
|
23
|
+
readonly model?: string | undefined;
|
|
24
|
+
readonly thinking?: AgentThinking | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ResolvedAgentConfig {
|
|
28
|
+
readonly harness: Harness;
|
|
29
|
+
readonly provider: ModelProviderRef;
|
|
30
|
+
readonly model: string;
|
|
31
|
+
readonly thinking: AgentThinking;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type AgentDefaultsResolver = (step: ContextualAgentConfig) => ResolvedAgentConfig;
|
|
35
|
+
|
|
36
|
+
export interface AgentConfigResolutionContext {
|
|
37
|
+
readonly workspaceDefaultHarnessId?: Harness | null | undefined;
|
|
38
|
+
readonly workspaceDefaultProviderId?: ModelProviderRef | null | undefined;
|
|
39
|
+
readonly workspaceProviderConfigs?: ReadonlyMap<ModelProviderRef, WorkspaceProviderDefaults>;
|
|
40
|
+
readonly instanceDefaultProvider?: SupportedModelProviderId | undefined;
|
|
41
|
+
readonly instanceDefaultModel?: string | undefined;
|
|
42
|
+
readonly instanceDefaultThinking?: AgentThinking | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface WorkspaceProviderDefaults {
|
|
46
|
+
readonly kind?: 'builtin' | 'custom' | undefined;
|
|
47
|
+
readonly defaultModel: string | null;
|
|
48
|
+
readonly defaultThinking: AgentThinking;
|
|
49
|
+
readonly models?: readonly CustomAgentModelDto[] | null | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type ModelCandidateResolver = () => string | null | undefined;
|
|
53
|
+
|
|
54
|
+
export function resolveAgentConfig(
|
|
55
|
+
step: ContextualAgentConfig,
|
|
56
|
+
ctx: AgentConfigResolutionContext = {},
|
|
57
|
+
): ResolvedAgentConfig {
|
|
58
|
+
const harness = step.harness ?? ctx.workspaceDefaultHarnessId ?? DEFAULT_HARNESS;
|
|
59
|
+
const provider = resolveProvider(step, ctx, harness);
|
|
60
|
+
const workspaceProviderConfig = ctx.workspaceProviderConfigs?.get(provider);
|
|
61
|
+
const model = resolveModel({
|
|
62
|
+
step,
|
|
63
|
+
ctx,
|
|
64
|
+
harness,
|
|
65
|
+
provider,
|
|
66
|
+
workspaceProviderConfig,
|
|
67
|
+
});
|
|
68
|
+
const thinking = resolveThinking({step, ctx, harness, provider, workspaceProviderConfig});
|
|
69
|
+
|
|
70
|
+
return {harness, provider, model, thinking};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const catalogDefaultAgentResolver: AgentDefaultsResolver = (step) =>
|
|
74
|
+
resolveAgentConfig(step);
|
|
75
|
+
|
|
76
|
+
function resolveProvider(
|
|
77
|
+
step: ContextualAgentConfig,
|
|
78
|
+
ctx: AgentConfigResolutionContext,
|
|
79
|
+
harness: Harness,
|
|
80
|
+
): ModelProviderRef {
|
|
81
|
+
const descriptor = getHarnessDescriptor(harness);
|
|
82
|
+
if (step.provider !== undefined) {
|
|
83
|
+
const provider = resolveSupportedProvider(step.provider, ctx);
|
|
84
|
+
if (!isHarnessCompatible(harness, provider, ctx.workspaceProviderConfigs?.get(provider))) {
|
|
85
|
+
throw new UnsupportedHarnessProviderError(
|
|
86
|
+
harness,
|
|
87
|
+
step.provider,
|
|
88
|
+
descriptor.supportedProviderIds,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return provider;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const candidates = [
|
|
95
|
+
ctx.workspaceDefaultProviderId,
|
|
96
|
+
ctx.instanceDefaultProvider,
|
|
97
|
+
descriptor.defaultProviderId,
|
|
98
|
+
];
|
|
99
|
+
for (const candidate of candidates) {
|
|
100
|
+
if (candidate === undefined || candidate === null) continue;
|
|
101
|
+
|
|
102
|
+
const workspaceProviderConfig = ctx.workspaceProviderConfigs?.get(candidate);
|
|
103
|
+
if (!isHarnessCompatible(harness, candidate, workspaceProviderConfig)) continue;
|
|
104
|
+
|
|
105
|
+
return resolveSupportedProvider(candidate, ctx);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return descriptor.defaultProviderId;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveSupportedProvider(
|
|
112
|
+
provider: string,
|
|
113
|
+
ctx: AgentConfigResolutionContext,
|
|
114
|
+
): ModelProviderRef {
|
|
115
|
+
const workspaceProviderConfig = ctx.workspaceProviderConfigs?.get(provider);
|
|
116
|
+
if (workspaceProviderConfig?.kind === 'custom') return provider;
|
|
117
|
+
|
|
118
|
+
const entry = getModelProviderEntry(provider);
|
|
119
|
+
if (entry === undefined || entry.support_status !== 'supported') {
|
|
120
|
+
throw new UnsupportedModelProviderError(provider);
|
|
121
|
+
}
|
|
122
|
+
return provider as SupportedModelProviderId;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveModel(params: {
|
|
126
|
+
step: ContextualAgentConfig;
|
|
127
|
+
ctx: AgentConfigResolutionContext;
|
|
128
|
+
harness: Harness;
|
|
129
|
+
provider: ModelProviderRef;
|
|
130
|
+
workspaceProviderConfig: WorkspaceProviderDefaults | undefined;
|
|
131
|
+
}): string {
|
|
132
|
+
if (params.step.model !== undefined) {
|
|
133
|
+
validateModel(
|
|
134
|
+
params.harness,
|
|
135
|
+
params.provider,
|
|
136
|
+
params.step.model,
|
|
137
|
+
params.workspaceProviderConfig,
|
|
138
|
+
);
|
|
139
|
+
return params.step.model;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const candidates: ModelCandidateResolver[] = [() => params.workspaceProviderConfig?.defaultModel];
|
|
143
|
+
if (params.workspaceProviderConfig?.kind === 'custom') {
|
|
144
|
+
candidates.push(() => customDefaultModel(params.workspaceProviderConfig));
|
|
145
|
+
} else {
|
|
146
|
+
candidates.push(
|
|
147
|
+
() => instanceDefaultModel(params.provider, params.ctx),
|
|
148
|
+
() => catalogDefaultModel(params.harness, params.provider),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const resolveCandidate of candidates) {
|
|
153
|
+
const candidate = resolveCandidate();
|
|
154
|
+
if (candidate === undefined || candidate === null) continue;
|
|
155
|
+
if (
|
|
156
|
+
modelIsAvailable(params.harness, params.provider, candidate, params.workspaceProviderConfig)
|
|
157
|
+
) {
|
|
158
|
+
return candidate;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
throw new InvalidAgentModelError(params.harness, params.provider, '');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function catalogDefaultModel(harness: Harness, provider: ModelProviderRef): string {
|
|
166
|
+
const catalogModels = listProviderModels(harness, provider);
|
|
167
|
+
const entry = getModelProviderEntry(provider);
|
|
168
|
+
if (entry === undefined || entry.support_status !== 'supported') {
|
|
169
|
+
throw new UnsupportedModelProviderError(provider);
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
entry.default_model !== null &&
|
|
173
|
+
catalogModels.some((candidate) => candidate.id === entry.default_model)
|
|
174
|
+
) {
|
|
175
|
+
return entry.default_model;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const firstModel = catalogModels[0];
|
|
179
|
+
if (firstModel === undefined) throw new InvalidAgentModelError(harness, provider, '');
|
|
180
|
+
return firstModel.id;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function instanceDefaultModel(
|
|
184
|
+
provider: ModelProviderRef,
|
|
185
|
+
ctx: AgentConfigResolutionContext,
|
|
186
|
+
): string | undefined {
|
|
187
|
+
return provider === ctx.instanceDefaultProvider ? ctx.instanceDefaultModel : undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function instanceDefaultThinking(
|
|
191
|
+
provider: ModelProviderRef,
|
|
192
|
+
ctx: AgentConfigResolutionContext,
|
|
193
|
+
): AgentThinking | undefined {
|
|
194
|
+
return provider === ctx.instanceDefaultProvider ? ctx.instanceDefaultThinking : undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function customDefaultModel(
|
|
198
|
+
workspaceProviderConfig: WorkspaceProviderDefaults | undefined,
|
|
199
|
+
): string | undefined {
|
|
200
|
+
if (workspaceProviderConfig?.kind !== 'custom') return undefined;
|
|
201
|
+
return workspaceProviderConfig.models?.[0]?.id;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function validateModel(
|
|
205
|
+
harness: Harness,
|
|
206
|
+
provider: ModelProviderRef,
|
|
207
|
+
model: string,
|
|
208
|
+
workspaceProviderConfig: WorkspaceProviderDefaults | undefined,
|
|
209
|
+
): void {
|
|
210
|
+
if (modelIsAvailable(harness, provider, model, workspaceProviderConfig)) return;
|
|
211
|
+
throw new InvalidAgentModelError(harness, provider, model);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function modelIsAvailable(
|
|
215
|
+
harness: Harness,
|
|
216
|
+
provider: ModelProviderRef,
|
|
217
|
+
model: string,
|
|
218
|
+
workspaceProviderConfig: WorkspaceProviderDefaults | undefined,
|
|
219
|
+
): boolean {
|
|
220
|
+
if (workspaceProviderConfig?.kind === 'custom') {
|
|
221
|
+
return workspaceProviderConfig.models?.some((candidate) => candidate.id === model) ?? false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return listProviderModels(harness, provider).some((candidate) => candidate.id === model);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function listProviderModels(harness: Harness, provider: ModelProviderRef): readonly {id: string}[] {
|
|
228
|
+
return listHarnessProviderModels(harness, provider);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isHarnessCompatible(
|
|
232
|
+
harness: Harness,
|
|
233
|
+
provider: ModelProviderRef,
|
|
234
|
+
workspaceProviderConfig: WorkspaceProviderDefaults | undefined,
|
|
235
|
+
): boolean {
|
|
236
|
+
if (workspaceProviderConfig?.kind === 'custom') return harness === 'pi';
|
|
237
|
+
|
|
238
|
+
return getHarnessDescriptor(harness).supportedProviderIds.includes(provider);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function resolveThinking(params: {
|
|
242
|
+
step: ContextualAgentConfig;
|
|
243
|
+
ctx: AgentConfigResolutionContext;
|
|
244
|
+
harness: Harness;
|
|
245
|
+
provider: ModelProviderRef;
|
|
246
|
+
workspaceProviderConfig: WorkspaceProviderDefaults | undefined;
|
|
247
|
+
}): AgentThinking {
|
|
248
|
+
const thinkingSchema = agentThinkingByHarness[params.harness];
|
|
249
|
+
const descriptor = getHarnessDescriptor(params.harness);
|
|
250
|
+
|
|
251
|
+
if (params.step.thinking !== undefined) {
|
|
252
|
+
if (!thinkingSchema.safeParse(params.step.thinking).success) {
|
|
253
|
+
throw new UnsupportedHarnessThinkingError(
|
|
254
|
+
params.harness,
|
|
255
|
+
params.step.thinking,
|
|
256
|
+
descriptor.thinkingLevels,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return params.step.thinking;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const candidates = [
|
|
263
|
+
params.workspaceProviderConfig?.defaultThinking,
|
|
264
|
+
instanceDefaultThinking(params.provider, params.ctx),
|
|
265
|
+
descriptor.defaultThinking,
|
|
266
|
+
];
|
|
267
|
+
for (const candidate of candidates) {
|
|
268
|
+
if (candidate !== undefined && thinkingSchema.safeParse(candidate).success) return candidate;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return descriptor.defaultThinking;
|
|
272
|
+
}
|