praxis-agent 0.43.1 → 0.45.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.
@@ -0,0 +1,71 @@
1
+ import type { ProviderTarget } from './provider-settings.js';
2
+ interface ProviderCredentialKey {
3
+ providerId: string;
4
+ profileId: string;
5
+ }
6
+ type ProviderCredentialRecord = {
7
+ type: 'api-key';
8
+ secret: string;
9
+ } | {
10
+ type: 'oauth';
11
+ accessToken: string;
12
+ refreshToken: string;
13
+ expiresAt: number;
14
+ accountId?: string;
15
+ };
16
+ export interface ProviderCredentialReader {
17
+ read(key: ProviderCredentialKey): Promise<ProviderCredentialRecord | undefined>;
18
+ }
19
+ export type ProviderAuthenticationErrorCode = 'missing_credential' | 'invalid_credential' | 'command_failed';
20
+ export declare class ProviderAuthenticationError extends Error {
21
+ readonly code: ProviderAuthenticationErrorCode;
22
+ constructor(code: ProviderAuthenticationErrorCode, message: string);
23
+ }
24
+ export type ProviderCredentialSourceMetadata = {
25
+ source: 'explicit';
26
+ } | {
27
+ source: 'legacy-env';
28
+ name: 'PRAXIS_API_KEY';
29
+ } | {
30
+ source: 'env';
31
+ name: string;
32
+ } | {
33
+ source: 'command';
34
+ command: readonly string[];
35
+ } | {
36
+ source: 'vault';
37
+ providerId: string;
38
+ profileId: string;
39
+ };
40
+ export type ResolvedApiKeyCredential = {
41
+ type: 'api-key';
42
+ secret: string;
43
+ source: ProviderCredentialSourceMetadata;
44
+ };
45
+ export type ResolvedOAuthCredential = {
46
+ type: 'oauth';
47
+ accessToken: string;
48
+ refreshToken: string;
49
+ expiresAt: number;
50
+ accountId?: string;
51
+ source: ProviderCredentialSourceMetadata;
52
+ };
53
+ export type ResolvedProviderCredential = ResolvedApiKeyCredential | ResolvedOAuthCredential;
54
+ export interface ProviderCommandResult {
55
+ stdout: string;
56
+ exitCode: number;
57
+ }
58
+ export type ProviderCommandRunner = (argv: readonly string[], options: {
59
+ timeoutMs: number;
60
+ maxOutputBytes: number;
61
+ }) => Promise<ProviderCommandResult>;
62
+ export interface ResolveProviderCredentialOptions {
63
+ target: ProviderTarget;
64
+ environment?: Readonly<Record<string, string | undefined>>;
65
+ vault: ProviderCredentialReader;
66
+ apiKey?: string;
67
+ commandRunner?: ProviderCommandRunner;
68
+ }
69
+ export declare function resolveProviderCredential(options: ResolveProviderCredentialOptions): Promise<ResolvedProviderCredential>;
70
+ export {};
71
+ //# sourceMappingURL=provider-auth.d.ts.map
@@ -0,0 +1,149 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ export class ProviderAuthenticationError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = 'ProviderAuthenticationError';
8
+ this.code = code;
9
+ }
10
+ }
11
+ const execFileAsync = promisify(execFile);
12
+ const COMMAND_TIMEOUT_MS = 10_000;
13
+ const MAX_COMMAND_OUTPUT = 64 * 1024;
14
+ function missing(message) {
15
+ throw new ProviderAuthenticationError('missing_credential', `Provider authentication failed: ${message}`);
16
+ }
17
+ function secretValue(value, label) {
18
+ if (typeof value !== 'string' || value.trim().length === 0)
19
+ missing(`${label} is missing or blank`);
20
+ return value;
21
+ }
22
+ const defaultCommandRunner = async (argv, options) => {
23
+ const executable = argv[0];
24
+ if (executable === undefined || executable.length === 0)
25
+ missing('credential command is empty');
26
+ try {
27
+ const result = await execFileAsync(executable, argv.slice(1), {
28
+ shell: false,
29
+ timeout: options.timeoutMs,
30
+ maxBuffer: options.maxOutputBytes,
31
+ });
32
+ return { stdout: result.stdout, exitCode: 0 };
33
+ }
34
+ catch (error) {
35
+ const stdout = typeof error.stdout === 'string'
36
+ ? error.stdout
37
+ : '';
38
+ const exitCode = typeof error.code === 'number'
39
+ ? error.code
40
+ : 1;
41
+ return { stdout, exitCode };
42
+ }
43
+ };
44
+ function keyFor(target, profileId = target.profileId) {
45
+ return { providerId: target.providerId, profileId };
46
+ }
47
+ async function vaultApiKey(target, vault, profileId) {
48
+ let record;
49
+ try {
50
+ record = await vault.read(keyFor(target, profileId));
51
+ }
52
+ catch {
53
+ missing(`vault credential for ${target.providerId}/${profileId} is unavailable`);
54
+ }
55
+ if (!record || record.type !== 'api-key')
56
+ missing(`vault API key for ${target.providerId}/${profileId} is unavailable`);
57
+ return {
58
+ type: 'api-key',
59
+ secret: record.secret,
60
+ source: { source: 'vault', providerId: target.providerId, profileId },
61
+ };
62
+ }
63
+ export async function resolveProviderCredential(options) {
64
+ const { target } = options;
65
+ const environment = options.environment ?? process.env;
66
+ const explicitProvided = options.apiKey !== undefined;
67
+ const legacyProvided = environment.PRAXIS_API_KEY !== undefined;
68
+ if (target.protocol === 'codex-subscription') {
69
+ if (explicitProvided || legacyProvided)
70
+ missing('Codex subscription does not accept API keys');
71
+ if (target.credential.source !== 'vault')
72
+ missing('Codex subscription requires a vault OAuth credential');
73
+ const profileId = target.credential.profile ?? target.profileId;
74
+ if (profileId !== target.profileId)
75
+ missing('Codex subscription vault profile must match the selected profile');
76
+ let record;
77
+ try {
78
+ record = await options.vault.read(keyFor(target, target.profileId));
79
+ }
80
+ catch {
81
+ missing(`vault credential for ${target.providerId}/${target.profileId} is unavailable`);
82
+ }
83
+ if (!record || record.type !== 'oauth')
84
+ missing(`vault OAuth credential for ${target.providerId}/${target.profileId} is unavailable`);
85
+ return {
86
+ type: 'oauth',
87
+ accessToken: record.accessToken,
88
+ refreshToken: record.refreshToken,
89
+ expiresAt: record.expiresAt,
90
+ ...(record.accountId === undefined
91
+ ? {}
92
+ : { accountId: record.accountId }),
93
+ source: {
94
+ source: 'vault',
95
+ providerId: target.providerId,
96
+ profileId: target.profileId,
97
+ },
98
+ };
99
+ }
100
+ if (explicitProvided) {
101
+ return {
102
+ type: 'api-key',
103
+ secret: secretValue(options.apiKey, 'explicit API key'),
104
+ source: { source: 'explicit' },
105
+ };
106
+ }
107
+ if (legacyProvided) {
108
+ return {
109
+ type: 'api-key',
110
+ secret: secretValue(environment.PRAXIS_API_KEY, 'PRAXIS_API_KEY'),
111
+ source: { source: 'legacy-env', name: 'PRAXIS_API_KEY' },
112
+ };
113
+ }
114
+ const source = target.credential;
115
+ if (source.source === 'env') {
116
+ return {
117
+ type: 'api-key',
118
+ secret: secretValue(environment[source.name], source.name),
119
+ source: { source: 'env', name: source.name },
120
+ };
121
+ }
122
+ if (source.source === 'command') {
123
+ const runner = options.commandRunner ?? defaultCommandRunner;
124
+ let result;
125
+ try {
126
+ result = await runner(source.command, {
127
+ timeoutMs: COMMAND_TIMEOUT_MS,
128
+ maxOutputBytes: MAX_COMMAND_OUTPUT,
129
+ });
130
+ }
131
+ catch {
132
+ missing('credential command failed');
133
+ }
134
+ if (result.exitCode !== 0)
135
+ missing('credential command exited unsuccessfully');
136
+ const output = secretValue(result.stdout, 'credential command output').trim();
137
+ if (output.length > MAX_COMMAND_OUTPUT)
138
+ missing('credential command output exceeds 64 KiB');
139
+ if (/\r?\n/u.test(output))
140
+ missing('credential command must return exactly one secret');
141
+ return {
142
+ type: 'api-key',
143
+ secret: output,
144
+ source: { source: 'command', command: source.command },
145
+ };
146
+ }
147
+ return vaultApiKey(target, options.vault, source.profile ?? target.profileId);
148
+ }
149
+ //# sourceMappingURL=provider-auth.js.map
@@ -0,0 +1,52 @@
1
+ import type { ModelProvider, ModelThinkingConfig } from '../core/runtime.js';
2
+ import { type CodexOAuthVault } from './codex-oauth.js';
3
+ import { type ProviderTarget } from './provider-settings.js';
4
+ import type { ProviderCredentialSourceMetadata, ProviderCredentialReader, ResolvedProviderCredential } from './provider-auth.js';
5
+ import { parseProviderEnvironment, type ContextEnvironment } from './environment.js';
6
+ import { type AnthropicPromptCachePolicy } from './anthropic-prompt-cache.js';
7
+ export type ProviderRegistryErrorCode = 'unsupported_provider';
8
+ export declare class ProviderRegistryError extends Error {
9
+ readonly code: ProviderRegistryErrorCode;
10
+ constructor(code: ProviderRegistryErrorCode, message: string);
11
+ }
12
+ export interface ProviderRegistryOptions {
13
+ target: ProviderTarget;
14
+ credential: ResolvedProviderCredential;
15
+ context?: ContextEnvironment;
16
+ anthropicThinking?: ModelThinkingConfig;
17
+ openAiThinking?: ModelThinkingConfig;
18
+ codexThinking?: ModelThinkingConfig;
19
+ anthropicPromptCacheResolver?: (target: {
20
+ baseUrl: string;
21
+ model: string;
22
+ }) => AnthropicPromptCachePolicy;
23
+ fetchImplementation?: typeof fetch;
24
+ providerEnvironment?: ReturnType<typeof parseProviderEnvironment>;
25
+ vault?: CodexOAuthVault;
26
+ }
27
+ export interface ResolveProviderRegistryOptions {
28
+ configRoot: string;
29
+ cwd: string;
30
+ environment?: Readonly<Record<string, string | undefined>>;
31
+ provider?: string;
32
+ profile?: string;
33
+ model?: string;
34
+ includeSettings?: boolean;
35
+ includeProjectSettings?: boolean;
36
+ apiKey?: string;
37
+ context?: ContextEnvironment;
38
+ anthropicThinking?: ModelThinkingConfig;
39
+ openAiThinking?: ModelThinkingConfig;
40
+ codexThinking?: ModelThinkingConfig;
41
+ vault: ProviderCredentialReader & CodexOAuthVault;
42
+ fetchImplementation?: typeof fetch;
43
+ }
44
+ export type ProviderRegistrySourceMetadata = ProviderCredentialSourceMetadata;
45
+ export interface ProviderRegistry {
46
+ readonly target: ProviderTarget;
47
+ readonly credentialSource: ProviderRegistrySourceMetadata;
48
+ create(modelId?: string): ModelProvider;
49
+ }
50
+ export declare function resolveProviderRegistry(options: ResolveProviderRegistryOptions): Promise<ProviderRegistry>;
51
+ export declare function createProviderRegistry(options: ProviderRegistryOptions): ProviderRegistry;
52
+ //# sourceMappingURL=provider-registry.d.ts.map
@@ -0,0 +1,187 @@
1
+ import { AnthropicCompatibleProvider } from './anthropic-compatible.js';
2
+ import { OpenAICompatibleProvider } from './openai-compatible.js';
3
+ import { CodexSubscriptionProvider } from './codex-subscription.js';
4
+ import { DeadlineModelProvider } from './deadline-provider.js';
5
+ import { CodexOAuthCredentialManager, } from './codex-oauth.js';
6
+ import { resolveProviderTarget, } from './provider-settings.js';
7
+ import { ProviderAuthenticationError, resolveProviderCredential, } from './provider-auth.js';
8
+ import { parseContextEnvironment, parseProviderEnvironment, } from './environment.js';
9
+ import { createAnthropicPromptCachePolicyResolver, } from './anthropic-prompt-cache.js';
10
+ export class ProviderRegistryError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.name = 'ProviderRegistryError';
15
+ this.code = code;
16
+ }
17
+ }
18
+ export async function resolveProviderRegistry(options) {
19
+ const environment = options.environment ?? process.env;
20
+ const target = await resolveProviderTarget({
21
+ configRoot: options.configRoot,
22
+ cwd: options.cwd,
23
+ environment,
24
+ ...(options.provider === undefined ? {} : { provider: options.provider }),
25
+ ...(options.profile === undefined ? {} : { profile: options.profile }),
26
+ ...(options.model === undefined ? {} : { model: options.model }),
27
+ ...(options.includeSettings === undefined
28
+ ? {}
29
+ : { includeSettings: options.includeSettings }),
30
+ ...(options.includeProjectSettings === undefined
31
+ ? {}
32
+ : { includeProjectSettings: options.includeProjectSettings }),
33
+ });
34
+ const credential = await resolveProviderCredential({
35
+ target,
36
+ environment,
37
+ vault: options.vault,
38
+ ...(options.apiKey === undefined ? {} : { apiKey: options.apiKey }),
39
+ });
40
+ const context = options.context ?? parseContextEnvironment(environment);
41
+ const controlsEnvironment = {
42
+ ...environment,
43
+ PRAXIS_PROVIDER: target.protocol === 'anthropic-messages' ? 'anthropic' : 'openai',
44
+ PRAXIS_BASE_URL: target.baseUrl,
45
+ };
46
+ const providerEnvironment = parseProviderEnvironment(controlsEnvironment);
47
+ const promptCacheResolver = target.protocol === 'anthropic-messages'
48
+ ? createAnthropicPromptCachePolicyResolver(controlsEnvironment)
49
+ : undefined;
50
+ return createProviderRegistry({
51
+ target,
52
+ credential,
53
+ context,
54
+ ...(options.anthropicThinking === undefined
55
+ ? {}
56
+ : { anthropicThinking: options.anthropicThinking }),
57
+ ...(options.openAiThinking === undefined
58
+ ? {}
59
+ : { openAiThinking: options.openAiThinking }),
60
+ ...(options.codexThinking === undefined
61
+ ? {}
62
+ : { codexThinking: options.codexThinking }),
63
+ ...(promptCacheResolver === undefined
64
+ ? {}
65
+ : { anthropicPromptCacheResolver: promptCacheResolver }),
66
+ ...(options.fetchImplementation === undefined
67
+ ? {}
68
+ : { fetchImplementation: options.fetchImplementation }),
69
+ providerEnvironment,
70
+ vault: options.vault,
71
+ });
72
+ }
73
+ export function createProviderRegistry(options) {
74
+ return new NativeProviderRegistry(options);
75
+ }
76
+ class NativeProviderRegistry {
77
+ options;
78
+ target;
79
+ credentialSource;
80
+ codexManager;
81
+ constructor(options) {
82
+ this.options = options;
83
+ this.target = options.target;
84
+ this.credentialSource = options.credential.source;
85
+ if (options.target.protocol === 'codex-subscription') {
86
+ if (options.credential.type !== 'oauth') {
87
+ throw new ProviderAuthenticationError('invalid_credential', 'Provider authentication failed: Codex subscription requires an OAuth credential');
88
+ }
89
+ if (!options.vault) {
90
+ throw new ProviderAuthenticationError('invalid_credential', 'Provider authentication failed: Codex subscription requires a credential vault');
91
+ }
92
+ this.codexManager = new CodexOAuthCredentialManager(options.vault, options.target.profileId, options.fetchImplementation === undefined
93
+ ? {}
94
+ : { fetchImplementation: options.fetchImplementation });
95
+ }
96
+ }
97
+ create(modelId = this.target.modelId) {
98
+ const target = { ...this.target, modelId };
99
+ if (target.protocol === 'codex-subscription') {
100
+ if (!this.codexManager)
101
+ throw new ProviderAuthenticationError('invalid_credential', 'Provider authentication failed: Codex subscription credentials are unavailable');
102
+ return this.withDeadline(new CodexSubscriptionProvider({
103
+ model: target.modelId,
104
+ access: this.codexManager.access.bind(this.codexManager),
105
+ ...(this.options.fetchImplementation === undefined
106
+ ? {}
107
+ : { fetchImplementation: this.options.fetchImplementation }),
108
+ ...(this.options.codexThinking === undefined
109
+ ? {}
110
+ : { thinking: this.options.codexThinking }),
111
+ }));
112
+ }
113
+ if (target.protocol === 'openai-compatible') {
114
+ if (this.options.credential.type !== 'api-key') {
115
+ throw new ProviderAuthenticationError('invalid_credential', 'Provider authentication failed: an API key is required');
116
+ }
117
+ return this.withDeadline(new OpenAICompatibleProvider({
118
+ baseUrl: target.baseUrl,
119
+ model: target.modelId,
120
+ apiKey: this.options.credential.secret,
121
+ ...(this.options.context?.contextWindowTokens === undefined
122
+ ? {}
123
+ : {
124
+ contextWindowTokens: this.options.context.contextWindowTokens,
125
+ }),
126
+ ...(this.options.openAiThinking === undefined
127
+ ? {}
128
+ : { thinking: this.options.openAiThinking }),
129
+ ...(this.options.fetchImplementation === undefined
130
+ ? {}
131
+ : { fetchImplementation: this.options.fetchImplementation }),
132
+ }));
133
+ }
134
+ if (target.protocol === 'anthropic-messages') {
135
+ if (this.options.credential.type !== 'api-key') {
136
+ throw new ProviderAuthenticationError('invalid_credential', 'Provider authentication failed: an API key is required');
137
+ }
138
+ return this.withDeadline(new AnthropicCompatibleProvider({
139
+ baseUrl: target.baseUrl,
140
+ model: target.modelId,
141
+ apiKey: this.options.credential.secret,
142
+ ...(this.options.context?.contextWindowTokens === undefined
143
+ ? {}
144
+ : {
145
+ contextWindowTokens: this.options.context.contextWindowTokens,
146
+ }),
147
+ ...(this.options.anthropicThinking === undefined
148
+ ? {}
149
+ : {
150
+ thinking: this.options.anthropicThinking,
151
+ }),
152
+ ...(this.options.anthropicPromptCacheResolver === undefined
153
+ ? {}
154
+ : {
155
+ promptCaching: this.options.anthropicPromptCacheResolver({
156
+ baseUrl: target.baseUrl,
157
+ model: target.modelId,
158
+ }),
159
+ }),
160
+ ...(this.options.providerEnvironment?.maxOutputTokens === undefined
161
+ ? {}
162
+ : {
163
+ maxOutputTokens: this.options.providerEnvironment.maxOutputTokens,
164
+ }),
165
+ ...(this.options.providerEnvironment?.anthropicVersion === undefined
166
+ ? {}
167
+ : {
168
+ anthropicVersion: this.options.providerEnvironment.anthropicVersion,
169
+ }),
170
+ ...(this.options.providerEnvironment?.webSearch === undefined
171
+ ? {}
172
+ : { webSearch: this.options.providerEnvironment.webSearch }),
173
+ ...(this.options.fetchImplementation === undefined
174
+ ? {}
175
+ : { fetchImplementation: this.options.fetchImplementation }),
176
+ }));
177
+ }
178
+ throw new ProviderRegistryError('unsupported_provider', `Unsupported provider protocol: ${target.protocol}`);
179
+ }
180
+ withDeadline(provider) {
181
+ const deadlineMs = this.options.providerEnvironment?.deadlineMs;
182
+ return deadlineMs === undefined
183
+ ? provider
184
+ : new DeadlineModelProvider({ provider, deadlineMs });
185
+ }
186
+ }
187
+ //# sourceMappingURL=provider-registry.js.map
@@ -0,0 +1,38 @@
1
+ export type ProviderProtocol = 'openai-compatible' | 'anthropic-messages' | 'codex-subscription';
2
+ export type CredentialSource = {
3
+ source: 'env';
4
+ name: string;
5
+ } | {
6
+ source: 'command';
7
+ command: readonly [string, ...string[]];
8
+ } | {
9
+ source: 'vault';
10
+ profile?: string;
11
+ };
12
+ export interface ProviderTarget {
13
+ providerId: string;
14
+ profileId: string;
15
+ modelId: string;
16
+ protocol: ProviderProtocol;
17
+ baseUrl: string;
18
+ credential: CredentialSource;
19
+ billingMode: 'api' | 'subscription';
20
+ experimental: boolean;
21
+ }
22
+ export interface ResolveProviderTargetOptions {
23
+ configRoot: string;
24
+ cwd: string;
25
+ environment?: Readonly<Record<string, string | undefined>>;
26
+ model?: string;
27
+ provider?: string;
28
+ profile?: string;
29
+ includeSettings?: boolean;
30
+ includeProjectSettings?: boolean;
31
+ }
32
+ export type ProviderSettingsErrorCode = 'invalid_settings' | 'model_required' | 'unknown_provider' | 'unknown_profile';
33
+ export declare class ProviderSettingsError extends Error {
34
+ readonly code: ProviderSettingsErrorCode;
35
+ constructor(code: ProviderSettingsErrorCode, message: string);
36
+ }
37
+ export declare function resolveProviderTarget(options: ResolveProviderTargetOptions): Promise<ProviderTarget>;
38
+ //# sourceMappingURL=provider-settings.d.ts.map