@renseiai/agentfactory 0.8.3 → 0.8.4
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/dist/src/config/index.d.ts +1 -1
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/config/repository-config.d.ts +53 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +23 -0
- package/dist/src/config/repository-config.test.js +91 -1
- package/dist/src/orchestrator/detect-work-type.test.d.ts +2 -0
- package/dist/src/orchestrator/detect-work-type.test.d.ts.map +1 -0
- package/dist/src/orchestrator/detect-work-type.test.js +62 -0
- package/dist/src/orchestrator/heartbeat-writer.test.d.ts +2 -0
- package/dist/src/orchestrator/heartbeat-writer.test.d.ts.map +1 -0
- package/dist/src/orchestrator/heartbeat-writer.test.js +139 -0
- package/dist/src/orchestrator/orchestrator-utils.test.d.ts +2 -0
- package/dist/src/orchestrator/orchestrator-utils.test.d.ts.map +1 -0
- package/dist/src/orchestrator/orchestrator-utils.test.js +41 -0
- package/dist/src/orchestrator/orchestrator.d.ts +25 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +92 -43
- package/dist/src/orchestrator/state-recovery.test.d.ts +2 -0
- package/dist/src/orchestrator/state-recovery.test.d.ts.map +1 -0
- package/dist/src/orchestrator/state-recovery.test.js +425 -0
- package/dist/src/orchestrator/types.d.ts +11 -1
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/providers/index.d.ts +71 -15
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +156 -28
- package/dist/src/providers/index.test.d.ts +2 -0
- package/dist/src/providers/index.test.d.ts.map +1 -0
- package/dist/src/providers/index.test.js +225 -0
- package/package.json +3 -3
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
* Agent Provider Factory
|
|
3
3
|
*
|
|
4
4
|
* Creates provider instances based on name.
|
|
5
|
-
* Supports provider selection via env vars
|
|
6
|
-
* AGENT_PROVIDER=claude (global default)
|
|
7
|
-
* AGENT_PROVIDER_SOCIAL=codex (per-project override)
|
|
8
|
-
* AGENT_PROVIDER_QA=amp (per-work-type override)
|
|
5
|
+
* Supports provider selection via env vars, config, labels, and mentions.
|
|
9
6
|
*
|
|
10
|
-
* Resolution order
|
|
7
|
+
* Resolution order (highest → lowest):
|
|
8
|
+
* 1. Issue label override (provider:codex)
|
|
9
|
+
* 2. Mention context override ("use codex", "@codex")
|
|
10
|
+
* 3. Config providers.byWorkType
|
|
11
|
+
* 4. Config providers.byProject
|
|
12
|
+
* 5. Env var AGENT_PROVIDER_{WORKTYPE}
|
|
13
|
+
* 6. Env var AGENT_PROVIDER_{PROJECT}
|
|
14
|
+
* 7. Config providers.default
|
|
15
|
+
* 8. Env var AGENT_PROVIDER
|
|
16
|
+
* 9. Hardcoded 'claude'
|
|
11
17
|
*/
|
|
12
18
|
export { ClaudeProvider, createClaudeProvider } from './claude-provider.js';
|
|
13
19
|
export { CodexProvider, createCodexProvider } from './codex-provider.js';
|
|
@@ -19,6 +25,18 @@ import { CodexProvider } from './codex-provider.js';
|
|
|
19
25
|
import { AmpProvider } from './amp-provider.js';
|
|
20
26
|
import { SpringAiProvider } from './spring-ai-provider.js';
|
|
21
27
|
import { A2aProvider } from './a2a-provider.js';
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Aliases — friendly names that map to real provider names
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
export const PROVIDER_ALIASES = {
|
|
32
|
+
opus: 'claude',
|
|
33
|
+
sonnet: 'claude',
|
|
34
|
+
codex: 'codex',
|
|
35
|
+
gemini: 'a2a',
|
|
36
|
+
};
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Provider factory
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
22
40
|
/**
|
|
23
41
|
* Create a provider instance by name.
|
|
24
42
|
*
|
|
@@ -39,47 +57,157 @@ export function createProvider(name) {
|
|
|
39
57
|
case 'a2a':
|
|
40
58
|
return new A2aProvider();
|
|
41
59
|
default:
|
|
42
|
-
throw new Error(`Unknown agent provider: ${name}. Supported: claude, codex, amp, spring-ai, a2a`
|
|
60
|
+
throw new Error(`Unknown agent provider: ${name}. Supported: claude, codex, amp, spring-ai, a2a. ` +
|
|
61
|
+
`If this is a CLI tool, ensure the binary is installed and on your PATH.`);
|
|
43
62
|
}
|
|
44
63
|
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Label & mention extraction
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
45
67
|
/**
|
|
46
|
-
*
|
|
68
|
+
* Extract provider name from issue labels.
|
|
69
|
+
* Looks for labels matching "provider:<name>" pattern.
|
|
70
|
+
*/
|
|
71
|
+
export function extractProviderFromLabels(labels) {
|
|
72
|
+
for (const label of labels) {
|
|
73
|
+
const match = label.match(/^provider:(\S+)$/i);
|
|
74
|
+
if (match) {
|
|
75
|
+
const resolved = resolveAlias(match[1]);
|
|
76
|
+
if (resolved && isValidProviderName(resolved)) {
|
|
77
|
+
return resolved;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Extract provider name from mention/prompt context text.
|
|
85
|
+
* Matches: "use <provider>", "@<provider>", "provider:<provider>"
|
|
86
|
+
* Case-insensitive, word-boundary aware.
|
|
87
|
+
*/
|
|
88
|
+
export function extractProviderFromMention(text) {
|
|
89
|
+
// Pattern 1: "use <provider>" (with word boundary to avoid "don't use")
|
|
90
|
+
const useMatch = text.match(/\buse\s+(\w[\w-]*)/i);
|
|
91
|
+
if (useMatch) {
|
|
92
|
+
const resolved = resolveAlias(useMatch[1]);
|
|
93
|
+
if (resolved && isValidProviderName(resolved)) {
|
|
94
|
+
return resolved;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Pattern 2: "@<provider>"
|
|
98
|
+
const atMatch = text.match(/@(\w[\w-]*)/i);
|
|
99
|
+
if (atMatch) {
|
|
100
|
+
const resolved = resolveAlias(atMatch[1]);
|
|
101
|
+
if (resolved && isValidProviderName(resolved)) {
|
|
102
|
+
return resolved;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Pattern 3: "provider:<provider>"
|
|
106
|
+
const providerMatch = text.match(/\bprovider:(\w[\w-]*)/i);
|
|
107
|
+
if (providerMatch) {
|
|
108
|
+
const resolved = resolveAlias(providerMatch[1]);
|
|
109
|
+
if (resolved && isValidProviderName(resolved)) {
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Provider resolution
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
/**
|
|
119
|
+
* Resolve which provider to use with full priority cascade.
|
|
47
120
|
*
|
|
48
|
-
* Resolution order (highest
|
|
49
|
-
* 1.
|
|
50
|
-
* 2.
|
|
51
|
-
* 3.
|
|
52
|
-
* 4.
|
|
121
|
+
* Resolution order (highest → lowest):
|
|
122
|
+
* 1. Issue label override (provider:codex)
|
|
123
|
+
* 2. Mention context override ("use codex", "@codex")
|
|
124
|
+
* 3. Config providers.byWorkType
|
|
125
|
+
* 4. Config providers.byProject
|
|
126
|
+
* 5. Env var AGENT_PROVIDER_{WORKTYPE}
|
|
127
|
+
* 6. Env var AGENT_PROVIDER_{PROJECT}
|
|
128
|
+
* 7. Config providers.default
|
|
129
|
+
* 8. Env var AGENT_PROVIDER
|
|
130
|
+
* 9. Hardcoded 'claude'
|
|
53
131
|
*
|
|
54
|
-
* @param
|
|
55
|
-
* @returns The resolved provider name
|
|
132
|
+
* @param context - Full resolution context (backwards-compatible with old { project, workType } shape)
|
|
133
|
+
* @returns The resolved provider name and its source
|
|
56
134
|
*/
|
|
57
|
-
export function
|
|
58
|
-
//
|
|
59
|
-
if (
|
|
60
|
-
const
|
|
135
|
+
export function resolveProviderWithSource(context) {
|
|
136
|
+
// 1. Issue label override
|
|
137
|
+
if (context?.labels?.length) {
|
|
138
|
+
const fromLabel = extractProviderFromLabels(context.labels);
|
|
139
|
+
if (fromLabel) {
|
|
140
|
+
return { name: fromLabel, source: `label provider:${fromLabel}` };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// 2. Mention context override
|
|
144
|
+
if (context?.mentionContext) {
|
|
145
|
+
const fromMention = extractProviderFromMention(context.mentionContext);
|
|
146
|
+
if (fromMention) {
|
|
147
|
+
return { name: fromMention, source: `mention "${context.mentionContext.substring(0, 30)}"` };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// 3. Config byWorkType
|
|
151
|
+
if (context?.workType && context?.configProviders?.byWorkType) {
|
|
152
|
+
const configWorkType = context.configProviders.byWorkType[context.workType];
|
|
153
|
+
if (configWorkType && isValidProviderName(configWorkType)) {
|
|
154
|
+
return { name: configWorkType, source: `config providers.byWorkType.${context.workType}` };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// 4. Config byProject
|
|
158
|
+
if (context?.project && context?.configProviders?.byProject) {
|
|
159
|
+
const configProject = context.configProviders.byProject[context.project];
|
|
160
|
+
if (configProject && isValidProviderName(configProject)) {
|
|
161
|
+
return { name: configProject, source: `config providers.byProject.${context.project}` };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// 5. Env var AGENT_PROVIDER_{WORKTYPE}
|
|
165
|
+
if (context?.workType) {
|
|
166
|
+
const workTypeKey = `AGENT_PROVIDER_${context.workType.toUpperCase().replace(/-/g, '_')}`;
|
|
61
167
|
const workTypeProvider = process.env[workTypeKey];
|
|
62
168
|
if (workTypeProvider && isValidProviderName(workTypeProvider)) {
|
|
63
|
-
return workTypeProvider;
|
|
169
|
+
return { name: workTypeProvider, source: `env ${workTypeKey}` };
|
|
64
170
|
}
|
|
65
171
|
}
|
|
66
|
-
//
|
|
67
|
-
if (
|
|
68
|
-
const projectKey = `AGENT_PROVIDER_${
|
|
172
|
+
// 6. Env var AGENT_PROVIDER_{PROJECT}
|
|
173
|
+
if (context?.project) {
|
|
174
|
+
const projectKey = `AGENT_PROVIDER_${context.project.toUpperCase()}`;
|
|
69
175
|
const projectProvider = process.env[projectKey];
|
|
70
176
|
if (projectProvider && isValidProviderName(projectProvider)) {
|
|
71
|
-
return projectProvider;
|
|
177
|
+
return { name: projectProvider, source: `env ${projectKey}` };
|
|
72
178
|
}
|
|
73
179
|
}
|
|
74
|
-
//
|
|
180
|
+
// 7. Config providers.default
|
|
181
|
+
if (context?.configProviders?.default && isValidProviderName(context.configProviders.default)) {
|
|
182
|
+
return { name: context.configProviders.default, source: 'config providers.default' };
|
|
183
|
+
}
|
|
184
|
+
// 8. Env var AGENT_PROVIDER
|
|
75
185
|
const globalProvider = process.env.AGENT_PROVIDER;
|
|
76
186
|
if (globalProvider && isValidProviderName(globalProvider)) {
|
|
77
|
-
return globalProvider;
|
|
187
|
+
return { name: globalProvider, source: 'env AGENT_PROVIDER' };
|
|
78
188
|
}
|
|
79
|
-
//
|
|
80
|
-
return 'claude';
|
|
189
|
+
// 9. Hardcoded fallback
|
|
190
|
+
return { name: 'claude', source: 'default' };
|
|
81
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Resolve which provider to use based on context.
|
|
194
|
+
* Backwards-compatible wrapper around resolveProviderWithSource().
|
|
195
|
+
*
|
|
196
|
+
* @param options - Project and work type context for resolution
|
|
197
|
+
* @returns The resolved provider name
|
|
198
|
+
*/
|
|
199
|
+
export function resolveProviderName(options) {
|
|
200
|
+
return resolveProviderWithSource(options).name;
|
|
201
|
+
}
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Internal helpers
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
82
205
|
const VALID_PROVIDER_NAMES = ['claude', 'codex', 'amp', 'spring-ai', 'a2a'];
|
|
83
|
-
function isValidProviderName(name) {
|
|
206
|
+
export function isValidProviderName(name) {
|
|
84
207
|
return VALID_PROVIDER_NAMES.includes(name);
|
|
85
208
|
}
|
|
209
|
+
/** Resolve an alias to a canonical provider name, or return the input if not an alias. */
|
|
210
|
+
function resolveAlias(name) {
|
|
211
|
+
const lower = name.toLowerCase();
|
|
212
|
+
return PROVIDER_ALIASES[lower] ?? lower;
|
|
213
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/providers/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { resolveProviderName, resolveProviderWithSource, extractProviderFromLabels, extractProviderFromMention, PROVIDER_ALIASES, isValidProviderName, } from './index.js';
|
|
3
|
+
describe('extractProviderFromLabels', () => {
|
|
4
|
+
it('extracts provider from "provider:<name>" label', () => {
|
|
5
|
+
expect(extractProviderFromLabels(['Bug', 'provider:codex', 'Feature'])).toBe('codex');
|
|
6
|
+
});
|
|
7
|
+
it('returns null when no provider label present', () => {
|
|
8
|
+
expect(extractProviderFromLabels(['Bug', 'Feature'])).toBeNull();
|
|
9
|
+
});
|
|
10
|
+
it('resolves aliases in labels', () => {
|
|
11
|
+
expect(extractProviderFromLabels(['provider:opus'])).toBe('claude');
|
|
12
|
+
expect(extractProviderFromLabels(['provider:sonnet'])).toBe('claude');
|
|
13
|
+
expect(extractProviderFromLabels(['provider:gemini'])).toBe('a2a');
|
|
14
|
+
});
|
|
15
|
+
it('is case-insensitive', () => {
|
|
16
|
+
expect(extractProviderFromLabels(['Provider:Codex'])).toBe('codex');
|
|
17
|
+
expect(extractProviderFromLabels(['PROVIDER:AMP'])).toBe('amp');
|
|
18
|
+
});
|
|
19
|
+
it('ignores invalid provider names', () => {
|
|
20
|
+
expect(extractProviderFromLabels(['provider:invalid'])).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
it('returns first match when multiple provider labels exist', () => {
|
|
23
|
+
expect(extractProviderFromLabels(['provider:codex', 'provider:amp'])).toBe('codex');
|
|
24
|
+
});
|
|
25
|
+
it('handles empty array', () => {
|
|
26
|
+
expect(extractProviderFromLabels([])).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('extractProviderFromMention', () => {
|
|
30
|
+
it('matches "use <provider>" pattern', () => {
|
|
31
|
+
expect(extractProviderFromMention('please use codex for this')).toBe('codex');
|
|
32
|
+
});
|
|
33
|
+
it('matches "@<provider>" pattern', () => {
|
|
34
|
+
expect(extractProviderFromMention('@codex handle this')).toBe('codex');
|
|
35
|
+
});
|
|
36
|
+
it('matches "provider:<provider>" pattern', () => {
|
|
37
|
+
expect(extractProviderFromMention('run with provider:amp')).toBe('amp');
|
|
38
|
+
});
|
|
39
|
+
it('resolves aliases in mentions', () => {
|
|
40
|
+
expect(extractProviderFromMention('use opus')).toBe('claude');
|
|
41
|
+
expect(extractProviderFromMention('@sonnet')).toBe('claude');
|
|
42
|
+
expect(extractProviderFromMention('provider:gemini')).toBe('a2a');
|
|
43
|
+
});
|
|
44
|
+
it('is case-insensitive', () => {
|
|
45
|
+
expect(extractProviderFromMention('use Codex')).toBe('codex');
|
|
46
|
+
expect(extractProviderFromMention('USE AMP')).toBe('amp');
|
|
47
|
+
});
|
|
48
|
+
it('returns null for invalid provider names', () => {
|
|
49
|
+
expect(extractProviderFromMention('use invalid')).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
it('returns null when no pattern matches', () => {
|
|
52
|
+
expect(extractProviderFromMention('fix the bug in login')).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
it('handles empty string', () => {
|
|
55
|
+
expect(extractProviderFromMention('')).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('resolveProviderWithSource — full cascade', () => {
|
|
59
|
+
const envBackup = {};
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
// Save and clear relevant env vars
|
|
62
|
+
for (const key of ['AGENT_PROVIDER', 'AGENT_PROVIDER_QA', 'AGENT_PROVIDER_DEVELOPMENT', 'AGENT_PROVIDER_SOCIAL', 'AGENT_PROVIDER_AGENT']) {
|
|
63
|
+
envBackup[key] = process.env[key];
|
|
64
|
+
delete process.env[key];
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
// Restore env vars
|
|
69
|
+
for (const [key, value] of Object.entries(envBackup)) {
|
|
70
|
+
if (value === undefined) {
|
|
71
|
+
delete process.env[key];
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
process.env[key] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
it('defaults to claude when no context provided', () => {
|
|
79
|
+
const result = resolveProviderWithSource();
|
|
80
|
+
expect(result).toEqual({ name: 'claude', source: 'default' });
|
|
81
|
+
});
|
|
82
|
+
it('1. label overrides everything', () => {
|
|
83
|
+
process.env.AGENT_PROVIDER = 'amp';
|
|
84
|
+
process.env.AGENT_PROVIDER_QA = 'amp';
|
|
85
|
+
const result = resolveProviderWithSource({
|
|
86
|
+
labels: ['provider:codex'],
|
|
87
|
+
mentionContext: 'use amp',
|
|
88
|
+
workType: 'qa',
|
|
89
|
+
project: 'Social',
|
|
90
|
+
configProviders: {
|
|
91
|
+
default: 'amp',
|
|
92
|
+
byWorkType: { qa: 'amp' },
|
|
93
|
+
byProject: { Social: 'amp' },
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
expect(result.name).toBe('codex');
|
|
97
|
+
expect(result.source).toContain('label');
|
|
98
|
+
});
|
|
99
|
+
it('2. mention overrides config and env', () => {
|
|
100
|
+
process.env.AGENT_PROVIDER = 'amp';
|
|
101
|
+
const result = resolveProviderWithSource({
|
|
102
|
+
mentionContext: 'use codex',
|
|
103
|
+
workType: 'qa',
|
|
104
|
+
configProviders: { byWorkType: { qa: 'amp' } },
|
|
105
|
+
});
|
|
106
|
+
expect(result.name).toBe('codex');
|
|
107
|
+
expect(result.source).toContain('mention');
|
|
108
|
+
});
|
|
109
|
+
it('3. config byWorkType overrides env and config defaults', () => {
|
|
110
|
+
process.env.AGENT_PROVIDER_QA = 'amp';
|
|
111
|
+
const result = resolveProviderWithSource({
|
|
112
|
+
workType: 'qa',
|
|
113
|
+
configProviders: { byWorkType: { qa: 'codex' }, default: 'amp' },
|
|
114
|
+
});
|
|
115
|
+
expect(result.name).toBe('codex');
|
|
116
|
+
expect(result.source).toContain('config providers.byWorkType.qa');
|
|
117
|
+
});
|
|
118
|
+
it('4. config byProject overrides env vars', () => {
|
|
119
|
+
process.env.AGENT_PROVIDER_SOCIAL = 'amp';
|
|
120
|
+
const result = resolveProviderWithSource({
|
|
121
|
+
project: 'Social',
|
|
122
|
+
configProviders: { byProject: { Social: 'codex' } },
|
|
123
|
+
});
|
|
124
|
+
expect(result.name).toBe('codex');
|
|
125
|
+
expect(result.source).toContain('config providers.byProject.Social');
|
|
126
|
+
});
|
|
127
|
+
it('5. env AGENT_PROVIDER_{WORKTYPE} overrides env project and defaults', () => {
|
|
128
|
+
process.env.AGENT_PROVIDER_QA = 'codex';
|
|
129
|
+
process.env.AGENT_PROVIDER_SOCIAL = 'amp';
|
|
130
|
+
process.env.AGENT_PROVIDER = 'amp';
|
|
131
|
+
const result = resolveProviderWithSource({
|
|
132
|
+
workType: 'qa',
|
|
133
|
+
project: 'Social',
|
|
134
|
+
});
|
|
135
|
+
expect(result.name).toBe('codex');
|
|
136
|
+
expect(result.source).toBe('env AGENT_PROVIDER_QA');
|
|
137
|
+
});
|
|
138
|
+
it('6. env AGENT_PROVIDER_{PROJECT} overrides global default', () => {
|
|
139
|
+
process.env.AGENT_PROVIDER_SOCIAL = 'codex';
|
|
140
|
+
process.env.AGENT_PROVIDER = 'amp';
|
|
141
|
+
const result = resolveProviderWithSource({
|
|
142
|
+
project: 'Social',
|
|
143
|
+
});
|
|
144
|
+
expect(result.name).toBe('codex');
|
|
145
|
+
expect(result.source).toBe('env AGENT_PROVIDER_SOCIAL');
|
|
146
|
+
});
|
|
147
|
+
it('7. config providers.default overrides env AGENT_PROVIDER', () => {
|
|
148
|
+
process.env.AGENT_PROVIDER = 'amp';
|
|
149
|
+
const result = resolveProviderWithSource({
|
|
150
|
+
configProviders: { default: 'codex' },
|
|
151
|
+
});
|
|
152
|
+
expect(result.name).toBe('codex');
|
|
153
|
+
expect(result.source).toBe('config providers.default');
|
|
154
|
+
});
|
|
155
|
+
it('8. env AGENT_PROVIDER overrides hardcoded default', () => {
|
|
156
|
+
process.env.AGENT_PROVIDER = 'codex';
|
|
157
|
+
const result = resolveProviderWithSource();
|
|
158
|
+
expect(result.name).toBe('codex');
|
|
159
|
+
expect(result.source).toBe('env AGENT_PROVIDER');
|
|
160
|
+
});
|
|
161
|
+
it('normalizes work type with hyphens to env var format', () => {
|
|
162
|
+
process.env.AGENT_PROVIDER_QA_COORDINATION = 'codex';
|
|
163
|
+
const result = resolveProviderWithSource({ workType: 'qa-coordination' });
|
|
164
|
+
expect(result.name).toBe('codex');
|
|
165
|
+
expect(result.source).toBe('env AGENT_PROVIDER_QA_COORDINATION');
|
|
166
|
+
delete process.env.AGENT_PROVIDER_QA_COORDINATION;
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe('resolveProviderName — backwards compatibility', () => {
|
|
170
|
+
const envBackup = {};
|
|
171
|
+
beforeEach(() => {
|
|
172
|
+
for (const key of ['AGENT_PROVIDER', 'AGENT_PROVIDER_QA', 'AGENT_PROVIDER_SOCIAL']) {
|
|
173
|
+
envBackup[key] = process.env[key];
|
|
174
|
+
delete process.env[key];
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
afterEach(() => {
|
|
178
|
+
for (const [key, value] of Object.entries(envBackup)) {
|
|
179
|
+
if (value === undefined) {
|
|
180
|
+
delete process.env[key];
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
process.env[key] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
it('returns claude by default', () => {
|
|
188
|
+
expect(resolveProviderName()).toBe('claude');
|
|
189
|
+
});
|
|
190
|
+
it('respects old { project, workType } shape', () => {
|
|
191
|
+
process.env.AGENT_PROVIDER_QA = 'codex';
|
|
192
|
+
expect(resolveProviderName({ workType: 'qa' })).toBe('codex');
|
|
193
|
+
});
|
|
194
|
+
it('accepts new ProviderResolutionContext shape', () => {
|
|
195
|
+
expect(resolveProviderName({ labels: ['provider:codex'] })).toBe('codex');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('PROVIDER_ALIASES', () => {
|
|
199
|
+
it('maps opus to claude', () => {
|
|
200
|
+
expect(PROVIDER_ALIASES['opus']).toBe('claude');
|
|
201
|
+
});
|
|
202
|
+
it('maps sonnet to claude', () => {
|
|
203
|
+
expect(PROVIDER_ALIASES['sonnet']).toBe('claude');
|
|
204
|
+
});
|
|
205
|
+
it('maps gemini to a2a', () => {
|
|
206
|
+
expect(PROVIDER_ALIASES['gemini']).toBe('a2a');
|
|
207
|
+
});
|
|
208
|
+
it('maps codex to codex', () => {
|
|
209
|
+
expect(PROVIDER_ALIASES['codex']).toBe('codex');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe('isValidProviderName', () => {
|
|
213
|
+
it('accepts valid provider names', () => {
|
|
214
|
+
expect(isValidProviderName('claude')).toBe(true);
|
|
215
|
+
expect(isValidProviderName('codex')).toBe(true);
|
|
216
|
+
expect(isValidProviderName('amp')).toBe(true);
|
|
217
|
+
expect(isValidProviderName('spring-ai')).toBe(true);
|
|
218
|
+
expect(isValidProviderName('a2a')).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
it('rejects invalid names', () => {
|
|
221
|
+
expect(isValidProviderName('invalid')).toBe(false);
|
|
222
|
+
expect(isValidProviderName('')).toBe(false);
|
|
223
|
+
expect(isValidProviderName('opus')).toBe(false); // alias, not a provider name
|
|
224
|
+
});
|
|
225
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@renseiai/agentfactory",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-agent fleet management for coding agents — orchestrator, providers, crash recovery",
|
|
6
6
|
"author": "Rensei AI (https://rensei.ai)",
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"handlebars": "^4.7.8",
|
|
51
51
|
"yaml": "^2.8.2",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
|
-
"@renseiai/agentfactory-linear": "0.8.
|
|
53
|
+
"@renseiai/agentfactory-linear": "0.8.4"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "^22.5.4",
|
|
57
57
|
"typescript": "^5.7.3",
|
|
58
58
|
"vitest": "^3.2.3",
|
|
59
|
-
"@renseiai/create-agentfactory-app": "0.8.
|
|
59
|
+
"@renseiai/create-agentfactory-app": "0.8.4"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsc",
|