byterover-cli 2.2.0 → 2.3.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/dist/agent/infra/llm/providers/openai.d.ts +12 -0
- package/dist/agent/infra/llm/providers/openai.js +52 -1
- package/dist/oclif/commands/curate/index.js +2 -2
- package/dist/oclif/commands/model/switch.js +14 -3
- package/dist/oclif/commands/providers/connect.d.ts +9 -0
- package/dist/oclif/commands/providers/connect.js +110 -14
- package/dist/oclif/commands/providers/list.js +3 -5
- package/dist/oclif/commands/query.js +2 -2
- package/dist/oclif/lib/daemon-client.d.ts +4 -0
- package/dist/oclif/lib/daemon-client.js +13 -3
- package/dist/server/core/domain/entities/provider-config.d.ts +6 -0
- package/dist/server/core/domain/entities/provider-config.js +4 -3
- package/dist/server/core/domain/entities/provider-registry.d.ts +41 -1
- package/dist/server/core/domain/entities/provider-registry.js +24 -1
- package/dist/server/core/domain/errors/task-error.d.ts +2 -0
- package/dist/server/core/domain/errors/task-error.js +6 -1
- package/dist/server/core/domain/transport/schemas.d.ts +2 -0
- package/dist/server/core/interfaces/i-provider-config-store.d.ts +2 -0
- package/dist/server/core/interfaces/i-provider-model-fetcher.d.ts +12 -3
- package/dist/server/core/interfaces/i-provider-oauth-token-store.d.ts +24 -0
- package/dist/server/core/interfaces/i-provider-oauth-token-store.js +1 -0
- package/dist/server/core/interfaces/i-token-refresh-manager.d.ts +7 -0
- package/dist/server/core/interfaces/i-token-refresh-manager.js +1 -0
- package/dist/server/infra/daemon/agent-process.js +22 -4
- package/dist/server/infra/daemon/brv-server.js +13 -2
- package/dist/server/infra/http/models-dev-client.d.ts +29 -0
- package/dist/server/infra/http/models-dev-client.js +133 -0
- package/dist/server/infra/http/provider-model-fetcher-registry.d.ts +2 -1
- package/dist/server/infra/http/provider-model-fetcher-registry.js +6 -1
- package/dist/server/infra/http/provider-model-fetchers.d.ts +33 -8
- package/dist/server/infra/http/provider-model-fetchers.js +88 -10
- package/dist/server/infra/process/feature-handlers.d.ts +3 -1
- package/dist/server/infra/process/feature-handlers.js +3 -1
- package/dist/server/infra/provider/provider-config-resolver.d.ts +4 -2
- package/dist/server/infra/provider/provider-config-resolver.js +59 -4
- package/dist/server/infra/provider-oauth/callback-server.d.ts +24 -0
- package/dist/server/infra/provider-oauth/callback-server.js +203 -0
- package/dist/server/infra/provider-oauth/errors.d.ts +39 -0
- package/dist/server/infra/provider-oauth/errors.js +76 -0
- package/dist/server/infra/provider-oauth/index.d.ts +9 -0
- package/dist/server/infra/provider-oauth/index.js +9 -0
- package/dist/server/infra/provider-oauth/jwt-utils.d.ts +17 -0
- package/dist/server/infra/provider-oauth/jwt-utils.js +51 -0
- package/dist/server/infra/provider-oauth/pkce-service.d.ts +22 -0
- package/dist/server/infra/provider-oauth/pkce-service.js +33 -0
- package/dist/server/infra/provider-oauth/provider-oauth-token-store.d.ts +48 -0
- package/dist/server/infra/provider-oauth/provider-oauth-token-store.js +155 -0
- package/dist/server/infra/provider-oauth/refresh-token-exchange.d.ts +8 -0
- package/dist/server/infra/provider-oauth/refresh-token-exchange.js +39 -0
- package/dist/server/infra/provider-oauth/token-exchange.d.ts +8 -0
- package/dist/server/infra/provider-oauth/token-exchange.js +44 -0
- package/dist/server/infra/provider-oauth/token-refresh-manager.d.ts +32 -0
- package/dist/server/infra/provider-oauth/token-refresh-manager.js +96 -0
- package/dist/server/infra/provider-oauth/types.d.ts +55 -0
- package/dist/server/infra/provider-oauth/types.js +22 -0
- package/dist/server/infra/storage/file-provider-config-store.d.ts +2 -0
- package/dist/server/infra/storage/file-provider-config-store.js +1 -3
- package/dist/server/infra/transport/handlers/model-handler.d.ts +3 -0
- package/dist/server/infra/transport/handlers/model-handler.js +53 -11
- package/dist/server/infra/transport/handlers/provider-handler.d.ts +26 -0
- package/dist/server/infra/transport/handlers/provider-handler.js +215 -13
- package/dist/shared/constants/oauth.d.ts +14 -0
- package/dist/shared/constants/oauth.js +14 -0
- package/dist/shared/transport/events/index.d.ts +5 -0
- package/dist/shared/transport/events/model-events.d.ts +2 -0
- package/dist/shared/transport/events/provider-events.d.ts +36 -0
- package/dist/shared/transport/events/provider-events.js +5 -0
- package/dist/shared/transport/types/dto.d.ts +4 -0
- package/dist/tui/features/model/api/set-active-model.d.ts +1 -1
- package/dist/tui/features/model/api/set-active-model.js +12 -4
- package/dist/tui/features/provider/api/await-oauth-callback.d.ts +11 -0
- package/dist/tui/features/provider/api/await-oauth-callback.js +25 -0
- package/dist/tui/features/provider/api/cancel-oauth.d.ts +5 -0
- package/dist/tui/features/provider/api/cancel-oauth.js +10 -0
- package/dist/tui/features/provider/api/start-oauth.d.ts +11 -0
- package/dist/tui/features/provider/api/start-oauth.js +15 -0
- package/dist/tui/features/provider/components/auth-method-dialog.d.ts +9 -0
- package/dist/tui/features/provider/components/auth-method-dialog.js +20 -0
- package/dist/tui/features/provider/components/oauth-dialog.d.ts +9 -0
- package/dist/tui/features/provider/components/oauth-dialog.js +96 -0
- package/dist/tui/features/provider/components/provider-dialog.js +1 -1
- package/dist/tui/features/provider/components/provider-flow.js +54 -4
- package/dist/tui/features/provider/components/provider-subscription-initializer.d.ts +1 -0
- package/dist/tui/features/provider/components/provider-subscription-initializer.js +5 -0
- package/dist/tui/features/provider/hooks/use-provider-subscriptions.d.ts +6 -0
- package/dist/tui/features/provider/hooks/use-provider-subscriptions.js +24 -0
- package/dist/tui/providers/app-providers.js +2 -1
- package/dist/tui/utils/error-messages.js +6 -1
- package/oclif.manifest.json +132 -116
- package/package.json +1 -1
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
* OpenAI Provider Module
|
|
3
3
|
*
|
|
4
4
|
* Direct access to GPT models via @ai-sdk/openai.
|
|
5
|
+
* Supports both standard OpenAI API and ChatGPT OAuth (Codex) endpoint.
|
|
5
6
|
*/
|
|
6
7
|
import type { ProviderModule } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Custom fetch wrapper for the ChatGPT OAuth endpoint.
|
|
10
|
+
*
|
|
11
|
+
* The ChatGPT OAuth Responses API has stricter requirements than the standard
|
|
12
|
+
* OpenAI API — certain fields are required and others are rejected:
|
|
13
|
+
* - `instructions` is required (system prompt — defaults to empty)
|
|
14
|
+
* - `store` must be false
|
|
15
|
+
* - `max_output_tokens` is not supported (must be omitted)
|
|
16
|
+
* - `id` fields on input items are rejected
|
|
17
|
+
*/
|
|
18
|
+
export declare function createChatGptOAuthFetch(): typeof globalThis.fetch;
|
|
7
19
|
export declare const openaiProvider: ProviderModule;
|
|
@@ -2,16 +2,67 @@
|
|
|
2
2
|
* OpenAI Provider Module
|
|
3
3
|
*
|
|
4
4
|
* Direct access to GPT models via @ai-sdk/openai.
|
|
5
|
+
* Supports both standard OpenAI API and ChatGPT OAuth (Codex) endpoint.
|
|
5
6
|
*/
|
|
6
7
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
8
|
+
import { CHATGPT_OAUTH_BASE_URL } from '../../../../shared/constants/oauth.js';
|
|
7
9
|
import { AiSdkContentGenerator } from '../generators/ai-sdk-content-generator.js';
|
|
10
|
+
/**
|
|
11
|
+
* Custom fetch wrapper for the ChatGPT OAuth endpoint.
|
|
12
|
+
*
|
|
13
|
+
* The ChatGPT OAuth Responses API has stricter requirements than the standard
|
|
14
|
+
* OpenAI API — certain fields are required and others are rejected:
|
|
15
|
+
* - `instructions` is required (system prompt — defaults to empty)
|
|
16
|
+
* - `store` must be false
|
|
17
|
+
* - `max_output_tokens` is not supported (must be omitted)
|
|
18
|
+
* - `id` fields on input items are rejected
|
|
19
|
+
*/
|
|
20
|
+
/* eslint-disable n/no-unsupported-features/node-builtins */
|
|
21
|
+
export function createChatGptOAuthFetch() {
|
|
22
|
+
return async (input, init) => {
|
|
23
|
+
if (init?.method === 'POST' && init.body) {
|
|
24
|
+
if (typeof init.body !== 'string') {
|
|
25
|
+
return globalThis.fetch(input, init);
|
|
26
|
+
}
|
|
27
|
+
let body;
|
|
28
|
+
try {
|
|
29
|
+
body = JSON.parse(init.body);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return globalThis.fetch(input, init);
|
|
33
|
+
}
|
|
34
|
+
if (!body.instructions) {
|
|
35
|
+
body.instructions = '';
|
|
36
|
+
}
|
|
37
|
+
body.store = false;
|
|
38
|
+
delete body.max_output_tokens;
|
|
39
|
+
if (Array.isArray(body.input)) {
|
|
40
|
+
for (const item of body.input) {
|
|
41
|
+
if (typeof item === 'object' && item !== null && 'id' in item) {
|
|
42
|
+
const record = item;
|
|
43
|
+
delete record.id;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
init = { ...init, body: JSON.stringify(body) };
|
|
48
|
+
}
|
|
49
|
+
return globalThis.fetch(input, init);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/* eslint-enable n/no-unsupported-features/node-builtins */
|
|
8
53
|
export const openaiProvider = {
|
|
9
54
|
apiKeyUrl: 'https://platform.openai.com/api-keys',
|
|
10
55
|
authType: 'api-key',
|
|
11
56
|
baseUrl: 'https://api.openai.com/v1',
|
|
12
57
|
category: 'popular',
|
|
13
58
|
createGenerator(config) {
|
|
14
|
-
const
|
|
59
|
+
const useChatGptOAuth = config.baseUrl === CHATGPT_OAUTH_BASE_URL;
|
|
60
|
+
const provider = createOpenAI({
|
|
61
|
+
apiKey: config.apiKey ?? '',
|
|
62
|
+
baseURL: config.baseUrl,
|
|
63
|
+
fetch: useChatGptOAuth ? createChatGptOAuthFetch() : undefined,
|
|
64
|
+
headers: config.headers,
|
|
65
|
+
});
|
|
15
66
|
return new AiSdkContentGenerator({
|
|
16
67
|
model: provider.responses(config.model),
|
|
17
68
|
});
|
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { TransportStateEventNames } from '../../../server/core/domain/transport/index.js';
|
|
4
4
|
import { extractCurateOperations } from '../../../server/utils/curate-result-parser.js';
|
|
5
5
|
import { TaskEvents } from '../../../shared/transport/events/index.js';
|
|
6
|
-
import { formatConnectionError, hasLeakedHandles, withDaemonRetry, } from '../../lib/daemon-client.js';
|
|
6
|
+
import { formatConnectionError, hasLeakedHandles, providerMissingMessage, withDaemonRetry, } from '../../lib/daemon-client.js';
|
|
7
7
|
import { writeJsonResponse } from '../../lib/json-response.js';
|
|
8
8
|
import { waitForTaskCompletion } from '../../lib/task-client.js';
|
|
9
9
|
export default class Curate extends Command {
|
|
@@ -91,7 +91,7 @@ Bad examples:
|
|
|
91
91
|
throw new Error('No provider connected. Run "brv providers connect byterover" to use the free built-in provider, or connect another provider.');
|
|
92
92
|
}
|
|
93
93
|
if (active.providerKeyMissing) {
|
|
94
|
-
throw new Error(
|
|
94
|
+
throw new Error(providerMissingMessage(active.activeProvider, active.authMethod));
|
|
95
95
|
}
|
|
96
96
|
await this.submitTask({ client, content: resolvedContent, flags, format, projectRoot, taskType });
|
|
97
97
|
}, {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import { ModelEvents
|
|
2
|
+
import { ModelEvents } from '../../../shared/transport/events/model-events.js';
|
|
3
3
|
import { ProviderEvents, } from '../../../shared/transport/events/provider-events.js';
|
|
4
4
|
import { withDaemonRetry } from '../../lib/daemon-client.js';
|
|
5
5
|
import { writeJsonResponse } from '../../lib/json-response.js';
|
|
@@ -42,7 +42,9 @@ export default class ModelSwitch extends Command {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
catch (error) {
|
|
45
|
-
const errorMessage = error instanceof Error
|
|
45
|
+
const errorMessage = error instanceof Error
|
|
46
|
+
? error.message
|
|
47
|
+
: 'An unexpected error occurred while switching the model. Please try again.';
|
|
46
48
|
if (format === 'json') {
|
|
47
49
|
writeJsonResponse({ command: 'model switch', data: { error: errorMessage }, success: false });
|
|
48
50
|
}
|
|
@@ -68,13 +70,22 @@ export default class ModelSwitch extends Command {
|
|
|
68
70
|
}
|
|
69
71
|
else {
|
|
70
72
|
const active = await client.requestWithAck(ProviderEvents.GET_ACTIVE);
|
|
73
|
+
if (!active.activeProviderId) {
|
|
74
|
+
throw new Error('No active provider configured. Run "brv providers connect <provider>" first.');
|
|
75
|
+
}
|
|
71
76
|
providerId = active.activeProviderId;
|
|
72
77
|
}
|
|
73
78
|
if (providerId === 'byterover') {
|
|
74
79
|
throw new Error('ByteRover provider uses its own internal LLM and does not support model switching. Run "brv providers switch <provider>" to switch to a different provider first.');
|
|
75
80
|
}
|
|
76
81
|
// 2. Switch active model
|
|
77
|
-
await client.requestWithAck(ModelEvents.SET_ACTIVE, {
|
|
82
|
+
const response = await client.requestWithAck(ModelEvents.SET_ACTIVE, {
|
|
83
|
+
modelId,
|
|
84
|
+
providerId,
|
|
85
|
+
});
|
|
86
|
+
if (!response.success) {
|
|
87
|
+
throw new Error(response.error ?? 'Failed to switch model');
|
|
88
|
+
}
|
|
78
89
|
return { modelId, providerId };
|
|
79
90
|
}, options);
|
|
80
91
|
}
|
|
@@ -9,8 +9,10 @@ export default class ProviderConnect extends Command {
|
|
|
9
9
|
static flags: {
|
|
10
10
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
code: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
model: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
oauth: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
16
|
};
|
|
15
17
|
protected connectProvider({ apiKey, baseUrl, model, providerId }: {
|
|
16
18
|
apiKey?: string;
|
|
@@ -22,5 +24,12 @@ export default class ProviderConnect extends Command {
|
|
|
22
24
|
providerId: string;
|
|
23
25
|
providerName: string;
|
|
24
26
|
}>;
|
|
27
|
+
protected connectProviderOAuth({ code, providerId }: {
|
|
28
|
+
code?: string;
|
|
29
|
+
providerId: string;
|
|
30
|
+
}, options?: DaemonClientOptions, onProgress?: (msg: string) => void): Promise<{
|
|
31
|
+
providerName: string;
|
|
32
|
+
showInstructions: boolean;
|
|
33
|
+
}>;
|
|
25
34
|
run(): Promise<void>;
|
|
26
35
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import {
|
|
2
|
+
import { OAUTH_CALLBACK_TIMEOUT_MS } from '../../../shared/constants/oauth.js';
|
|
3
|
+
import { ModelEvents } from '../../../shared/transport/events/model-events.js';
|
|
3
4
|
import { ProviderEvents, } from '../../../shared/transport/events/provider-events.js';
|
|
4
5
|
import { withDaemonRetry } from '../../lib/daemon-client.js';
|
|
5
6
|
import { writeJsonResponse } from '../../lib/json-response.js';
|
|
@@ -14,6 +15,7 @@ export default class ProviderConnect extends Command {
|
|
|
14
15
|
static examples = [
|
|
15
16
|
'<%= config.bin %> providers connect anthropic --api-key sk-xxx',
|
|
16
17
|
'<%= config.bin %> providers connect openai --api-key sk-xxx --model gpt-4.1',
|
|
18
|
+
'<%= config.bin %> providers connect openai --oauth',
|
|
17
19
|
'<%= config.bin %> providers connect byterover',
|
|
18
20
|
'<%= config.bin %> providers connect openai-compatible --base-url http://localhost:11434/v1',
|
|
19
21
|
'<%= config.bin %> providers connect openai-compatible --base-url http://localhost:11434/v1 --api-key sk-xxx --model llama3',
|
|
@@ -27,6 +29,12 @@ export default class ProviderConnect extends Command {
|
|
|
27
29
|
char: 'b',
|
|
28
30
|
description: 'Base URL for OpenAI-compatible providers (e.g., http://localhost:11434/v1)',
|
|
29
31
|
}),
|
|
32
|
+
code: Flags.string({
|
|
33
|
+
char: 'c',
|
|
34
|
+
description: 'Authorization code for code-paste OAuth providers (e.g., Anthropic). ' +
|
|
35
|
+
'Not applicable to browser-callback providers like OpenAI — use --oauth without --code instead.',
|
|
36
|
+
hidden: true,
|
|
37
|
+
}),
|
|
30
38
|
format: Flags.string({
|
|
31
39
|
default: 'text',
|
|
32
40
|
description: 'Output format (text or json)',
|
|
@@ -36,6 +44,10 @@ export default class ProviderConnect extends Command {
|
|
|
36
44
|
char: 'm',
|
|
37
45
|
description: 'Model to set as active after connecting',
|
|
38
46
|
}),
|
|
47
|
+
oauth: Flags.boolean({
|
|
48
|
+
default: false,
|
|
49
|
+
description: 'Connect via OAuth (browser-based)',
|
|
50
|
+
}),
|
|
39
51
|
};
|
|
40
52
|
async connectProvider({ apiKey, baseUrl, model, providerId }, options) {
|
|
41
53
|
return withDaemonRetry(async (client) => {
|
|
@@ -48,8 +60,8 @@ export default class ProviderConnect extends Command {
|
|
|
48
60
|
// 2. Validate base URL for openai-compatible
|
|
49
61
|
if (providerId === 'openai-compatible') {
|
|
50
62
|
if (!baseUrl && !provider.isConnected) {
|
|
51
|
-
throw new Error('Provider "openai-compatible" requires a base URL. Use the --base-url flag to provide one.'
|
|
52
|
-
|
|
63
|
+
throw new Error('Provider "openai-compatible" requires a base URL. Use the --base-url flag to provide one.' +
|
|
64
|
+
'\nExample: brv providers connect openai-compatible --base-url http://localhost:11434/v1');
|
|
53
65
|
}
|
|
54
66
|
if (baseUrl) {
|
|
55
67
|
let parsed;
|
|
@@ -72,8 +84,8 @@ export default class ProviderConnect extends Command {
|
|
|
72
84
|
}
|
|
73
85
|
}
|
|
74
86
|
else if (!apiKey && provider.requiresApiKey && !provider.isConnected) {
|
|
75
|
-
throw new Error(`Provider "${providerId}" requires an API key. Use the --api-key flag to provide one.`
|
|
76
|
-
|
|
87
|
+
throw new Error(`Provider "${providerId}" requires an API key. Use the --api-key flag to provide one.` +
|
|
88
|
+
(provider.apiKeyUrl ? `\nDon't have one? Get your API key at: ${provider.apiKeyUrl}` : ''));
|
|
77
89
|
}
|
|
78
90
|
// 4. Connect or switch active provider
|
|
79
91
|
const hasNewConfig = apiKey || baseUrl;
|
|
@@ -87,27 +99,111 @@ export default class ProviderConnect extends Command {
|
|
|
87
99
|
return { model, providerId, providerName: provider.name };
|
|
88
100
|
}, options);
|
|
89
101
|
}
|
|
102
|
+
async connectProviderOAuth({ code, providerId }, options, onProgress) {
|
|
103
|
+
return withDaemonRetry(async (client) => {
|
|
104
|
+
// 1. Verify provider exists and supports OAuth
|
|
105
|
+
const { providers } = await client.requestWithAck(ProviderEvents.LIST);
|
|
106
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
107
|
+
if (!provider) {
|
|
108
|
+
throw new Error(`Unknown provider "${providerId}". Run "brv providers list" to see available providers.`);
|
|
109
|
+
}
|
|
110
|
+
if (!provider.supportsOAuth) {
|
|
111
|
+
throw new Error(`Provider "${providerId}" does not support OAuth. Use --api-key instead.`);
|
|
112
|
+
}
|
|
113
|
+
// --code is only valid for code-paste providers (e.g., Anthropic).
|
|
114
|
+
// Browser-callback providers like OpenAI handle the code exchange automatically.
|
|
115
|
+
if (code && provider.oauthCallbackMode !== 'code-paste') {
|
|
116
|
+
throw new Error(`Provider "${providerId}" uses browser-based OAuth and does not accept --code.\n` +
|
|
117
|
+
`Run: brv providers connect ${providerId} --oauth`);
|
|
118
|
+
}
|
|
119
|
+
// If --code is provided, submit it directly (code-paste providers)
|
|
120
|
+
if (code) {
|
|
121
|
+
const response = await client.requestWithAck(ProviderEvents.SUBMIT_OAUTH_CODE, { code, providerId });
|
|
122
|
+
if (!response.success) {
|
|
123
|
+
throw new Error(response.error ?? 'OAuth code submission failed');
|
|
124
|
+
}
|
|
125
|
+
return { providerName: provider.name, showInstructions: false };
|
|
126
|
+
}
|
|
127
|
+
// 2. Start OAuth flow — returns immediately with auth URL
|
|
128
|
+
const startResponse = await client.requestWithAck(ProviderEvents.START_OAUTH, {
|
|
129
|
+
providerId,
|
|
130
|
+
});
|
|
131
|
+
if (!startResponse.success) {
|
|
132
|
+
throw new Error(startResponse.error ?? 'Failed to start OAuth flow');
|
|
133
|
+
}
|
|
134
|
+
// Always print auth URL (user's machine may not support browser launch)
|
|
135
|
+
onProgress?.(`\nOpen this URL to authenticate:\n ${startResponse.authUrl}\n`);
|
|
136
|
+
// 3. Handle based on callback mode
|
|
137
|
+
if (startResponse.callbackMode === 'auto') {
|
|
138
|
+
onProgress?.('Waiting for authentication in browser...');
|
|
139
|
+
const awaitResponse = await client.requestWithAck(ProviderEvents.AWAIT_OAUTH_CALLBACK, { providerId }, { timeout: OAUTH_CALLBACK_TIMEOUT_MS });
|
|
140
|
+
if (!awaitResponse.success) {
|
|
141
|
+
throw new Error(awaitResponse.error ?? 'OAuth authentication failed');
|
|
142
|
+
}
|
|
143
|
+
return { providerName: provider.name, showInstructions: false };
|
|
144
|
+
}
|
|
145
|
+
// code-paste mode: print instructions and exit
|
|
146
|
+
onProgress?.('Copy the authorization code from the browser and run:');
|
|
147
|
+
onProgress?.(` brv providers connect ${providerId} --oauth --code <code>`);
|
|
148
|
+
return { providerName: provider.name, showInstructions: true };
|
|
149
|
+
}, options);
|
|
150
|
+
}
|
|
90
151
|
async run() {
|
|
91
152
|
const { args, flags } = await this.parse(ProviderConnect);
|
|
92
153
|
const providerId = args.provider;
|
|
93
154
|
const apiKey = flags['api-key'];
|
|
94
155
|
const baseUrl = flags['base-url'];
|
|
95
|
-
const { model } = flags;
|
|
96
|
-
const format = flags.format;
|
|
97
|
-
|
|
98
|
-
|
|
156
|
+
const { code, model, oauth } = flags;
|
|
157
|
+
const format = flags.format === 'json' ? 'json' : 'text';
|
|
158
|
+
// Validate flag combinations
|
|
159
|
+
if (oauth && apiKey) {
|
|
160
|
+
const msg = 'Cannot use --oauth and --api-key together';
|
|
161
|
+
if (format === 'json') {
|
|
162
|
+
writeJsonResponse({ command: 'providers connect', data: { error: msg }, success: false });
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.log(msg);
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (code && !oauth) {
|
|
170
|
+
const msg = '--code requires the --oauth flag';
|
|
99
171
|
if (format === 'json') {
|
|
100
|
-
writeJsonResponse({ command: 'providers connect', data:
|
|
172
|
+
writeJsonResponse({ command: 'providers connect', data: { error: msg }, success: false });
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.log(msg);
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
if (oauth) {
|
|
181
|
+
const onProgress = format === 'text' ? (msg) => this.log(msg) : undefined;
|
|
182
|
+
const result = await this.connectProviderOAuth({ code, providerId }, undefined, onProgress);
|
|
183
|
+
if (format === 'json') {
|
|
184
|
+
writeJsonResponse({ command: 'providers connect', data: { providerId }, success: true });
|
|
185
|
+
}
|
|
186
|
+
else if (!result.showInstructions) {
|
|
187
|
+
this.log(`Connected to ${result.providerName} via OAuth`);
|
|
188
|
+
}
|
|
101
189
|
}
|
|
102
190
|
else {
|
|
103
|
-
this.
|
|
104
|
-
if (
|
|
105
|
-
|
|
191
|
+
const result = await this.connectProvider({ apiKey, baseUrl, model, providerId });
|
|
192
|
+
if (format === 'json') {
|
|
193
|
+
writeJsonResponse({ command: 'providers connect', data: result, success: true });
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.log(`Connected to ${result.providerName} (${result.providerId})`);
|
|
197
|
+
if (result.model) {
|
|
198
|
+
this.log(`Model set to: ${result.model}`);
|
|
199
|
+
}
|
|
106
200
|
}
|
|
107
201
|
}
|
|
108
202
|
}
|
|
109
203
|
catch (error) {
|
|
110
|
-
const errorMessage = error instanceof Error
|
|
204
|
+
const errorMessage = error instanceof Error
|
|
205
|
+
? error.message
|
|
206
|
+
: 'An unexpected error occurred while connecting the provider. Please try again.';
|
|
111
207
|
if (format === 'json') {
|
|
112
208
|
writeJsonResponse({ command: 'providers connect', data: { error: errorMessage }, success: false });
|
|
113
209
|
}
|
|
@@ -5,10 +5,7 @@ import { formatConnectionError, withDaemonRetry } from '../../lib/daemon-client.
|
|
|
5
5
|
import { writeJsonResponse } from '../../lib/json-response.js';
|
|
6
6
|
export default class ProviderList extends Command {
|
|
7
7
|
static description = 'List all available providers and their connection status';
|
|
8
|
-
static examples = [
|
|
9
|
-
'<%= config.bin %> providers list',
|
|
10
|
-
'<%= config.bin %> providers list --format json',
|
|
11
|
-
];
|
|
8
|
+
static examples = ['<%= config.bin %> providers list', '<%= config.bin %> providers list --format json'];
|
|
12
9
|
static flags = {
|
|
13
10
|
format: Flags.string({
|
|
14
11
|
default: 'text',
|
|
@@ -30,7 +27,8 @@ export default class ProviderList extends Command {
|
|
|
30
27
|
}
|
|
31
28
|
for (const p of providers) {
|
|
32
29
|
const status = p.isCurrent ? chalk.green('(current)') : p.isConnected ? chalk.yellow('(connected)') : '';
|
|
33
|
-
|
|
30
|
+
const authBadge = p.authMethod === 'oauth' ? chalk.cyan('[OAuth]') : p.authMethod === 'api-key' ? chalk.dim('[API Key]') : '';
|
|
31
|
+
this.log(` ${p.name} [${p.id}] ${status} ${authBadge}`.trimEnd());
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
34
|
catch (error) {
|
|
@@ -2,7 +2,7 @@ import { Args, Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { TransportStateEventNames } from '../../server/core/domain/transport/schemas.js';
|
|
4
4
|
import { TaskEvents } from '../../shared/transport/events/index.js';
|
|
5
|
-
import { formatConnectionError, hasLeakedHandles, withDaemonRetry, } from '../lib/daemon-client.js';
|
|
5
|
+
import { formatConnectionError, hasLeakedHandles, providerMissingMessage, withDaemonRetry, } from '../lib/daemon-client.js';
|
|
6
6
|
import { writeJsonResponse } from '../lib/json-response.js';
|
|
7
7
|
import { waitForTaskCompletion } from '../lib/task-client.js';
|
|
8
8
|
export default class Query extends Command {
|
|
@@ -54,7 +54,7 @@ Bad:
|
|
|
54
54
|
throw new Error('No provider connected. Run "brv providers connect byterover" to use the free built-in provider, or connect another provider.');
|
|
55
55
|
}
|
|
56
56
|
if (active.providerKeyMissing) {
|
|
57
|
-
throw new Error(
|
|
57
|
+
throw new Error(providerMissingMessage(active.activeProvider, active.authMethod));
|
|
58
58
|
}
|
|
59
59
|
await this.submitTask({ client, format, projectRoot, query: args.query });
|
|
60
60
|
}, {
|
|
@@ -30,6 +30,10 @@ export declare function isRetryableError(error: unknown): boolean;
|
|
|
30
30
|
* Checks if an error left leaked Socket.IO handles that prevent Node.js from exiting.
|
|
31
31
|
*/
|
|
32
32
|
export declare function hasLeakedHandles(error: unknown): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Builds a user-friendly message when provider credentials are missing from storage.
|
|
35
|
+
*/
|
|
36
|
+
export declare function providerMissingMessage(activeProvider: string, authMethod?: 'api-key' | 'oauth'): string;
|
|
33
37
|
export interface ProviderErrorContext {
|
|
34
38
|
activeModel?: string;
|
|
35
39
|
activeProvider?: string;
|
|
@@ -12,6 +12,8 @@ const USER_FRIENDLY_MESSAGES = {
|
|
|
12
12
|
[TaskErrorCode.CONTEXT_TREE_NOT_INITIALIZED]: 'Context tree not initialized.',
|
|
13
13
|
[TaskErrorCode.LOCAL_CHANGES_EXIST]: 'You have local changes. Run "brv push" to save your changes before pulling.',
|
|
14
14
|
[TaskErrorCode.NOT_AUTHENTICATED]: 'Not authenticated. Cloud sync features (push/pull/space) require login — local query and curate work without authentication.',
|
|
15
|
+
[TaskErrorCode.OAUTH_REFRESH_FAILED]: 'OAuth token refresh failed. Run "brv providers connect <provider> --oauth" to reconnect.',
|
|
16
|
+
[TaskErrorCode.OAUTH_TOKEN_EXPIRED]: 'OAuth token has expired. Run "brv providers connect <provider> --oauth" to reconnect.',
|
|
15
17
|
[TaskErrorCode.PROJECT_NOT_INIT]: 'Project not initialized. Run "brv restart" to reinitialize.',
|
|
16
18
|
[TaskErrorCode.PROVIDER_NOT_CONFIGURED]: 'No provider connected. Run "brv providers connect byterover" to use the free built-in provider, or connect another provider.',
|
|
17
19
|
[TaskErrorCode.SPACE_NOT_CONFIGURED]: 'No space configured. Run "brv space list" to see available spaces, then "brv space switch --team <team> --name <space>" to select one.',
|
|
@@ -83,6 +85,14 @@ export function hasLeakedHandles(error) {
|
|
|
83
85
|
return false;
|
|
84
86
|
return error.code === TaskErrorCode.AGENT_DISCONNECTED || error.code === TaskErrorCode.AGENT_NOT_AVAILABLE;
|
|
85
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Builds a user-friendly message when provider credentials are missing from storage.
|
|
90
|
+
*/
|
|
91
|
+
export function providerMissingMessage(activeProvider, authMethod) {
|
|
92
|
+
return authMethod === 'oauth'
|
|
93
|
+
? `${activeProvider} authentication has expired.\nPlease reconnect: brv providers connect ${activeProvider} --oauth`
|
|
94
|
+
: `${activeProvider} API key is missing from storage.\nPlease reconnect: brv providers connect ${activeProvider} --api-key <your-key>`;
|
|
95
|
+
}
|
|
86
96
|
/**
|
|
87
97
|
* Formats a connection error into a user-friendly message.
|
|
88
98
|
*/
|
|
@@ -131,9 +141,9 @@ export function formatConnectionError(error, providerContext) {
|
|
|
131
141
|
const provider = providerContext?.activeProvider ?? '<provider>';
|
|
132
142
|
const model = providerContext?.activeModel;
|
|
133
143
|
const currentInfo = model ? `Provider: ${provider} Model: ${model}\n\n` : `Provider: ${provider}\n\n`;
|
|
134
|
-
return (`LLM provider
|
|
135
|
-
' Reconnect
|
|
136
|
-
` brv providers connect ${provider}
|
|
144
|
+
return (`LLM provider credentials are missing or invalid.\n${currentInfo}` +
|
|
145
|
+
' Reconnect your provider:\n' +
|
|
146
|
+
` brv providers connect ${provider}\n\n` +
|
|
137
147
|
' Switch to a different provider:\n' +
|
|
138
148
|
' brv providers switch <provider>\n\n' +
|
|
139
149
|
' See all options: brv providers --help');
|
|
@@ -12,12 +12,16 @@ export interface ConnectedProviderConfig {
|
|
|
12
12
|
readonly activeModel?: string;
|
|
13
13
|
/** Context window size of the active model (from provider API, e.g. OpenRouter) */
|
|
14
14
|
readonly activeModelContextLength?: number;
|
|
15
|
+
/** How this provider was authenticated */
|
|
16
|
+
readonly authMethod?: 'api-key' | 'oauth';
|
|
15
17
|
/** Custom API base URL (for openai-compatible provider) */
|
|
16
18
|
readonly baseUrl?: string;
|
|
17
19
|
/** When the provider was connected */
|
|
18
20
|
readonly connectedAt: string;
|
|
19
21
|
/** User's favorite models (for quick access) */
|
|
20
22
|
readonly favoriteModels: readonly string[];
|
|
23
|
+
/** OAuth account ID (e.g. ChatGPT-Account-Id for OpenAI) */
|
|
24
|
+
readonly oauthAccountId?: string;
|
|
21
25
|
/** Recently used models (last 10) */
|
|
22
26
|
readonly recentModels: readonly string[];
|
|
23
27
|
}
|
|
@@ -96,7 +100,9 @@ export declare class ProviderConfig {
|
|
|
96
100
|
*/
|
|
97
101
|
withProviderConnected(providerId: string, options?: {
|
|
98
102
|
activeModel?: string;
|
|
103
|
+
authMethod?: 'api-key' | 'oauth';
|
|
99
104
|
baseUrl?: string;
|
|
105
|
+
oauthAccountId?: string;
|
|
100
106
|
}): ProviderConfig;
|
|
101
107
|
/**
|
|
102
108
|
* Create a new config with a provider disconnected.
|
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
const isProviderConfigJson = (json) => {
|
|
11
11
|
if (typeof json !== 'object' || json === null)
|
|
12
12
|
return false;
|
|
13
|
-
|
|
14
|
-
if (typeof obj.activeProvider !== 'string')
|
|
13
|
+
if (!('activeProvider' in json) || typeof json.activeProvider !== 'string')
|
|
15
14
|
return false;
|
|
16
|
-
if (typeof
|
|
15
|
+
if (!('providers' in json) || typeof json.providers !== 'object' || json.providers === null)
|
|
17
16
|
return false;
|
|
18
17
|
return true;
|
|
19
18
|
};
|
|
@@ -164,9 +163,11 @@ export class ProviderConfig {
|
|
|
164
163
|
const existingConfig = this.providers[providerId];
|
|
165
164
|
const newProviderConfig = {
|
|
166
165
|
activeModel: options?.activeModel ?? existingConfig?.activeModel,
|
|
166
|
+
authMethod: options?.authMethod ?? existingConfig?.authMethod,
|
|
167
167
|
baseUrl: options?.baseUrl ?? existingConfig?.baseUrl,
|
|
168
168
|
connectedAt: existingConfig?.connectedAt ?? new Date().toISOString(),
|
|
169
169
|
favoriteModels: existingConfig?.favoriteModels ?? [],
|
|
170
|
+
oauthAccountId: options?.oauthAccountId ?? existingConfig?.oauthAccountId,
|
|
170
171
|
recentModels: existingConfig?.recentModels ?? [],
|
|
171
172
|
};
|
|
172
173
|
return new ProviderConfig({
|
|
@@ -4,6 +4,44 @@
|
|
|
4
4
|
* Defines available LLM providers that can be connected to byterover-cli.
|
|
5
5
|
* Inspired by OpenCode's provider system.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for a single OAuth authentication mode.
|
|
9
|
+
*/
|
|
10
|
+
export interface OAuthModeConfig {
|
|
11
|
+
/** Auth URL for this mode */
|
|
12
|
+
readonly authUrl: string;
|
|
13
|
+
/** Mode identifier (e.g. 'default', 'pro-max') */
|
|
14
|
+
readonly id: string;
|
|
15
|
+
/** Display label (e.g. "Sign in with OpenAI") */
|
|
16
|
+
readonly label: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* OAuth configuration for a provider.
|
|
20
|
+
*/
|
|
21
|
+
export interface ProviderOAuthConfig {
|
|
22
|
+
/** How the callback is received: local server ('auto') or user pastes code ('code-paste') */
|
|
23
|
+
readonly callbackMode: 'auto' | 'code-paste';
|
|
24
|
+
/** Port for local callback server (auto mode only) */
|
|
25
|
+
readonly callbackPort?: number;
|
|
26
|
+
/** OAuth client ID */
|
|
27
|
+
readonly clientId: string;
|
|
28
|
+
/** Whether to add `code=true` query param to auth URL (code-paste mode only — tells server to display paste-able code) */
|
|
29
|
+
readonly codeDisplay?: boolean;
|
|
30
|
+
/** Default model when connected via OAuth (overrides ProviderDefinition.defaultModel) */
|
|
31
|
+
readonly defaultModel?: string;
|
|
32
|
+
/** Extra query params appended to the authorization URL (provider-specific) */
|
|
33
|
+
readonly extraParams?: Readonly<Record<string, string>>;
|
|
34
|
+
/** Supported OAuth modes (some providers have multiple) */
|
|
35
|
+
readonly modes: readonly OAuthModeConfig[];
|
|
36
|
+
/** OAuth redirect URI */
|
|
37
|
+
readonly redirectUri: string;
|
|
38
|
+
/** OAuth scopes */
|
|
39
|
+
readonly scopes: string;
|
|
40
|
+
/** Token endpoint content type: OpenAI = 'form', Anthropic = 'json' */
|
|
41
|
+
readonly tokenContentType: 'form' | 'json';
|
|
42
|
+
/** Token exchange endpoint */
|
|
43
|
+
readonly tokenUrl: string;
|
|
44
|
+
}
|
|
7
45
|
/**
|
|
8
46
|
* Definition for an LLM provider.
|
|
9
47
|
*/
|
|
@@ -28,6 +66,8 @@ export interface ProviderDefinition {
|
|
|
28
66
|
readonly modelsEndpoint: string;
|
|
29
67
|
/** Display name */
|
|
30
68
|
readonly name: string;
|
|
69
|
+
/** OAuth configuration (only for OAuth-capable providers) */
|
|
70
|
+
readonly oauth?: ProviderOAuthConfig;
|
|
31
71
|
/** Priority for display order (lower = higher priority) */
|
|
32
72
|
readonly priority: number;
|
|
33
73
|
}
|
|
@@ -54,4 +94,4 @@ export declare function getProviderById(id: string): ProviderDefinition | undefi
|
|
|
54
94
|
/**
|
|
55
95
|
* Check if a provider requires an API key.
|
|
56
96
|
*/
|
|
57
|
-
export declare function providerRequiresApiKey(id: string): boolean;
|
|
97
|
+
export declare function providerRequiresApiKey(id: string, authMethod?: 'api-key' | 'oauth'): boolean;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Defines available LLM providers that can be connected to byterover-cli.
|
|
5
5
|
* Inspired by OpenCode's provider system.
|
|
6
6
|
*/
|
|
7
|
+
import { CHATGPT_OAUTH_ORIGINATOR } from '../../../../shared/constants/oauth.js';
|
|
7
8
|
/**
|
|
8
9
|
* Registry of all available providers.
|
|
9
10
|
* Order by priority for consistent display.
|
|
@@ -160,6 +161,26 @@ export const PROVIDER_REGISTRY = {
|
|
|
160
161
|
id: 'openai',
|
|
161
162
|
modelsEndpoint: '/models',
|
|
162
163
|
name: 'OpenAI',
|
|
164
|
+
oauth: {
|
|
165
|
+
callbackMode: 'auto',
|
|
166
|
+
callbackPort: 1455,
|
|
167
|
+
// Public OAuth client ID (safe to commit — native app public client, no client secret)
|
|
168
|
+
clientId: 'app_EMoamEEZ73f0CkXaXp7hrann',
|
|
169
|
+
// OpenAI Codex model used for the ChatGPT OAuth (Codex CLI) flow
|
|
170
|
+
defaultModel: 'gpt-5.1-codex-mini',
|
|
171
|
+
/* eslint-disable camelcase -- OAuth query params follow RFC 6749 naming */
|
|
172
|
+
extraParams: {
|
|
173
|
+
codex_cli_simplified_flow: 'true',
|
|
174
|
+
id_token_add_organizations: 'true',
|
|
175
|
+
originator: CHATGPT_OAUTH_ORIGINATOR,
|
|
176
|
+
},
|
|
177
|
+
/* eslint-enable camelcase */
|
|
178
|
+
modes: [{ authUrl: 'https://auth.openai.com/oauth/authorize', id: 'default', label: 'Sign in with OpenAI' }],
|
|
179
|
+
redirectUri: 'http://localhost:1455/auth/callback',
|
|
180
|
+
scopes: 'openid profile email offline_access',
|
|
181
|
+
tokenContentType: 'form',
|
|
182
|
+
tokenUrl: 'https://auth.openai.com/oauth/token',
|
|
183
|
+
},
|
|
163
184
|
priority: 3,
|
|
164
185
|
},
|
|
165
186
|
'openai-compatible': {
|
|
@@ -268,7 +289,9 @@ export function getProviderById(id) {
|
|
|
268
289
|
/**
|
|
269
290
|
* Check if a provider requires an API key.
|
|
270
291
|
*/
|
|
271
|
-
export function providerRequiresApiKey(id) {
|
|
292
|
+
export function providerRequiresApiKey(id, authMethod) {
|
|
293
|
+
if (authMethod === 'oauth')
|
|
294
|
+
return false;
|
|
272
295
|
const provider = getProviderById(id);
|
|
273
296
|
if (!provider)
|
|
274
297
|
return false;
|
|
@@ -11,6 +11,8 @@ export declare const TaskErrorCode: {
|
|
|
11
11
|
readonly LLM_RATE_LIMIT: "ERR_LLM_RATE_LIMIT";
|
|
12
12
|
readonly LOCAL_CHANGES_EXIST: "ERR_LOCAL_CHANGES_EXIST";
|
|
13
13
|
readonly NOT_AUTHENTICATED: "ERR_NOT_AUTHENTICATED";
|
|
14
|
+
readonly OAUTH_REFRESH_FAILED: "ERR_OAUTH_REFRESH_FAILED";
|
|
15
|
+
readonly OAUTH_TOKEN_EXPIRED: "ERR_OAUTH_TOKEN_EXPIRED";
|
|
14
16
|
readonly PROJECT_NOT_INIT: "ERR_PROJECT_NOT_INIT";
|
|
15
17
|
readonly PROVIDER_NOT_CONFIGURED: "ERR_PROVIDER_NOT_CONFIGURED";
|
|
16
18
|
readonly SPACE_NOT_CONFIGURED: "ERR_SPACE_NOT_CONFIGURED";
|
|
@@ -15,6 +15,9 @@ export const TaskErrorCode = {
|
|
|
15
15
|
LOCAL_CHANGES_EXIST: 'ERR_LOCAL_CHANGES_EXIST',
|
|
16
16
|
// Auth/Init errors
|
|
17
17
|
NOT_AUTHENTICATED: 'ERR_NOT_AUTHENTICATED',
|
|
18
|
+
// OAuth errors
|
|
19
|
+
OAUTH_REFRESH_FAILED: 'ERR_OAUTH_REFRESH_FAILED',
|
|
20
|
+
OAUTH_TOKEN_EXPIRED: 'ERR_OAUTH_TOKEN_EXPIRED',
|
|
18
21
|
// Execution errors
|
|
19
22
|
PROJECT_NOT_INIT: 'ERR_PROJECT_NOT_INIT',
|
|
20
23
|
PROVIDER_NOT_CONFIGURED: 'ERR_PROVIDER_NOT_CONFIGURED',
|
|
@@ -100,7 +103,9 @@ export class AgentDisconnectedError extends TaskError {
|
|
|
100
103
|
}
|
|
101
104
|
export class AgentNotInitializedError extends TaskError {
|
|
102
105
|
constructor(reason) {
|
|
103
|
-
super(reason
|
|
106
|
+
super(reason
|
|
107
|
+
? `Agent failed to initialize: ${reason}`
|
|
108
|
+
: "Agent failed to initialize. Run 'brv restart' to force a clean restart.", TaskErrorCode.AGENT_NOT_INITIALIZED);
|
|
104
109
|
this.name = 'AgentNotInitializedError';
|
|
105
110
|
}
|
|
106
111
|
}
|
|
@@ -463,6 +463,8 @@ export declare const TransportDaemonEventNames: {
|
|
|
463
463
|
export interface ProviderConfigResponse {
|
|
464
464
|
activeModel?: string;
|
|
465
465
|
activeProvider: string;
|
|
466
|
+
/** How the provider was authenticated ('api-key' | 'oauth'). Undefined for internal providers. */
|
|
467
|
+
authMethod?: 'api-key' | 'oauth';
|
|
466
468
|
maxInputTokens?: number;
|
|
467
469
|
openRouterApiKey?: string;
|
|
468
470
|
provider?: string;
|