@ottocode/server 0.1.225 → 0.1.227

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.
@@ -1,6 +1,7 @@
1
1
  import type { Hono } from 'hono';
2
2
  import {
3
3
  getAllAuth,
4
+ getAuth,
4
5
  setAuth,
5
6
  removeAuth,
6
7
  ensureSetuWallet,
@@ -8,6 +9,7 @@ import {
8
9
  importWallet,
9
10
  loadConfig,
10
11
  catalog,
12
+ readEnvKey,
11
13
  getOnboardingComplete,
12
14
  setOnboardingComplete,
13
15
  authorize,
@@ -20,6 +22,7 @@ import {
20
22
  pollForCopilotTokenOnce,
21
23
  type ProviderId,
22
24
  } from '@ottocode/sdk';
25
+ import { execFileSync, spawnSync } from 'node:child_process';
23
26
  import { logger } from '@ottocode/sdk';
24
27
  import { serializeError } from '../runtime/errors/api-error.ts';
25
28
 
@@ -33,6 +36,172 @@ const copilotDeviceSessions = new Map<
33
36
  { deviceCode: string; interval: number; provider: string; createdAt: number }
34
37
  >();
35
38
 
39
+ const COPILOT_MODELS_URL = 'https://api.githubcopilot.com/models';
40
+ const GH_CAPABILITY_CACHE_TTL_MS = 60 * 1000;
41
+
42
+ let ghCapabilityCache: {
43
+ expiresAt: number;
44
+ value: { available: boolean; authenticated: boolean; reason?: string };
45
+ } = {
46
+ expiresAt: 0,
47
+ value: {
48
+ available: false,
49
+ authenticated: false,
50
+ reason: 'Not checked yet',
51
+ },
52
+ };
53
+
54
+ function getGhImportCapability() {
55
+ if (ghCapabilityCache.expiresAt > Date.now()) return ghCapabilityCache.value;
56
+
57
+ const version = spawnSync('gh', ['--version'], {
58
+ encoding: 'utf8',
59
+ stdio: ['ignore', 'pipe', 'pipe'],
60
+ });
61
+ if (version.status !== 0) {
62
+ ghCapabilityCache = {
63
+ expiresAt: Date.now() + GH_CAPABILITY_CACHE_TTL_MS,
64
+ value: {
65
+ available: false,
66
+ authenticated: false,
67
+ reason: 'GitHub CLI (gh) is not installed',
68
+ },
69
+ };
70
+ return ghCapabilityCache.value;
71
+ }
72
+
73
+ const authStatus = spawnSync('gh', ['auth', 'status', '-h', 'github.com'], {
74
+ encoding: 'utf8',
75
+ stdio: ['ignore', 'pipe', 'pipe'],
76
+ });
77
+ if (authStatus.status !== 0) {
78
+ ghCapabilityCache = {
79
+ expiresAt: Date.now() + GH_CAPABILITY_CACHE_TTL_MS,
80
+ value: {
81
+ available: true,
82
+ authenticated: false,
83
+ reason: 'Run `gh auth login` first',
84
+ },
85
+ };
86
+ return ghCapabilityCache.value;
87
+ }
88
+
89
+ ghCapabilityCache = {
90
+ expiresAt: Date.now() + GH_CAPABILITY_CACHE_TTL_MS,
91
+ value: {
92
+ available: true,
93
+ authenticated: true,
94
+ },
95
+ };
96
+ return ghCapabilityCache.value;
97
+ }
98
+
99
+ function parseErrorMessageFromBody(text: string): string | undefined {
100
+ if (!text) return undefined;
101
+ try {
102
+ const parsed = JSON.parse(text) as {
103
+ message?: string;
104
+ error?: { message?: string };
105
+ };
106
+ return parsed.error?.message ?? parsed.message;
107
+ } catch {
108
+ return undefined;
109
+ }
110
+ }
111
+
112
+ async function fetchCopilotModels(token: string): Promise<
113
+ | {
114
+ ok: true;
115
+ models: Set<string>;
116
+ }
117
+ | {
118
+ ok: false;
119
+ status: number;
120
+ message: string;
121
+ }
122
+ > {
123
+ try {
124
+ const response = await fetch(COPILOT_MODELS_URL, {
125
+ headers: {
126
+ Authorization: `Bearer ${token}`,
127
+ 'Openai-Intent': 'conversation-edits',
128
+ 'User-Agent': 'ottocode',
129
+ },
130
+ });
131
+ const text = await response.text();
132
+ if (!response.ok) {
133
+ return {
134
+ ok: false,
135
+ status: response.status,
136
+ message:
137
+ parseErrorMessageFromBody(text) ||
138
+ `Copilot models endpoint returned ${response.status}`,
139
+ };
140
+ }
141
+
142
+ const payload = JSON.parse(text) as {
143
+ data?: Array<{ id?: string }>;
144
+ };
145
+ const models = new Set(
146
+ (payload.data ?? [])
147
+ .map((item) => item.id)
148
+ .filter((id): id is string => Boolean(id)),
149
+ );
150
+ return { ok: true, models };
151
+ } catch (error) {
152
+ const message =
153
+ error instanceof Error ? error.message : 'Failed to fetch Copilot models';
154
+ return { ok: false, status: 0, message };
155
+ }
156
+ }
157
+
158
+ async function detectOAuthOrgRestriction(token: string): Promise<{
159
+ restricted: boolean;
160
+ org?: string;
161
+ message?: string;
162
+ }> {
163
+ try {
164
+ const orgsResponse = await fetch('https://api.github.com/user/orgs', {
165
+ headers: {
166
+ Authorization: `Bearer ${token}`,
167
+ 'User-Agent': 'ottocode',
168
+ Accept: 'application/vnd.github+json',
169
+ },
170
+ });
171
+ if (!orgsResponse.ok) {
172
+ return { restricted: false };
173
+ }
174
+
175
+ const orgs = (await orgsResponse.json()) as Array<{ login?: string }>;
176
+ for (const org of orgs) {
177
+ if (!org.login) continue;
178
+ const membershipResponse = await fetch(
179
+ `https://api.github.com/user/memberships/orgs/${org.login}`,
180
+ {
181
+ headers: {
182
+ Authorization: `Bearer ${token}`,
183
+ 'User-Agent': 'ottocode',
184
+ Accept: 'application/vnd.github+json',
185
+ },
186
+ },
187
+ );
188
+ if (membershipResponse.status !== 403) continue;
189
+
190
+ const bodyText = await membershipResponse.text();
191
+ const message = parseErrorMessageFromBody(bodyText) || bodyText;
192
+ if (message.includes('enabled OAuth App access restrictions')) {
193
+ return {
194
+ restricted: true,
195
+ org: org.login,
196
+ message,
197
+ };
198
+ }
199
+ }
200
+ } catch {}
201
+
202
+ return { restricted: false };
203
+ }
204
+
36
205
  setInterval(() => {
37
206
  const now = Date.now();
38
207
  for (const [key, value] of oauthVerifiers.entries()) {
@@ -55,6 +224,7 @@ export function registerAuthRoutes(app: Hono) {
55
224
  const cfg = await loadConfig(projectRoot);
56
225
  const onboardingComplete = await getOnboardingComplete(projectRoot);
57
226
  const setuWallet = await getSetuWallet(projectRoot);
227
+ const ghImportCapability = getGhImportCapability();
58
228
 
59
229
  const providers: Record<
60
230
  string,
@@ -63,6 +233,8 @@ export function registerAuthRoutes(app: Hono) {
63
233
  type?: 'api' | 'oauth' | 'wallet';
64
234
  label: string;
65
235
  supportsOAuth: boolean;
236
+ supportsToken?: boolean;
237
+ supportsGhImport?: boolean;
66
238
  modelCount: number;
67
239
  costRange?: { min: number; max: number };
68
240
  }
@@ -81,6 +253,9 @@ export function registerAuthRoutes(app: Hono) {
81
253
  label: entry.label || id,
82
254
  supportsOAuth:
83
255
  id === 'anthropic' || id === 'openai' || id === 'copilot',
256
+ supportsToken: id === 'copilot',
257
+ supportsGhImport:
258
+ id === 'copilot' ? ghImportCapability.available : false,
84
259
  modelCount: models.length,
85
260
  costRange:
86
261
  costs.length > 0
@@ -213,15 +388,15 @@ export function registerAuthRoutes(app: Hono) {
213
388
  app.post('/v1/auth/:provider/oauth/url', async (c) => {
214
389
  try {
215
390
  const provider = c.req.param('provider');
216
- const { mode = 'max' } = await c.req
217
- .json<{ mode?: string }>()
218
- .catch(() => ({}));
391
+ const body = await c.req.json<{ mode?: string }>().catch(() => undefined);
392
+ const mode: 'max' | 'console' =
393
+ body?.mode === 'console' ? 'console' : 'max';
219
394
 
220
395
  let url: string;
221
396
  let verifier: string;
222
397
 
223
398
  if (provider === 'anthropic') {
224
- const result = await authorize(mode as 'max' | 'console');
399
+ const result = await authorize(mode);
225
400
  url = result.url;
226
401
  verifier = result.verifier;
227
402
  } else if (provider === 'openai') {
@@ -585,6 +760,215 @@ export function registerAuthRoutes(app: Hono) {
585
760
  }
586
761
  });
587
762
 
763
+ app.get('/v1/auth/copilot/methods', async (c) => {
764
+ const ghImport = getGhImportCapability();
765
+ return c.json({
766
+ oauth: true,
767
+ token: true,
768
+ ghImport,
769
+ });
770
+ });
771
+
772
+ app.post('/v1/auth/copilot/token', async (c) => {
773
+ try {
774
+ const { token } = await c.req.json<{ token: string }>();
775
+ const sanitized = token?.trim();
776
+ if (!sanitized) {
777
+ return c.json({ error: 'Copilot token is required' }, 400);
778
+ }
779
+
780
+ const modelsResult = await fetchCopilotModels(sanitized);
781
+ if (!modelsResult.ok) {
782
+ return c.json(
783
+ {
784
+ error: `Invalid Copilot token: ${modelsResult.message}`,
785
+ },
786
+ 400,
787
+ );
788
+ }
789
+
790
+ await setAuth(
791
+ 'copilot',
792
+ {
793
+ type: 'oauth',
794
+ refresh: sanitized,
795
+ access: sanitized,
796
+ expires: 0,
797
+ },
798
+ undefined,
799
+ 'global',
800
+ );
801
+
802
+ const models = Array.from(modelsResult.models).sort();
803
+ return c.json({
804
+ success: true,
805
+ provider: 'copilot',
806
+ source: 'token',
807
+ modelCount: models.length,
808
+ hasGpt52Codex: modelsResult.models.has('gpt-5.2-codex'),
809
+ sampleModels: models.slice(0, 25),
810
+ });
811
+ } catch (error) {
812
+ const message =
813
+ error instanceof Error ? error.message : 'Failed to save Copilot token';
814
+ logger.error('Failed to save Copilot token', error);
815
+ return c.json({ error: message }, 500);
816
+ }
817
+ });
818
+
819
+ app.post('/v1/auth/copilot/gh/import', async (c) => {
820
+ try {
821
+ const ghImport = getGhImportCapability();
822
+ if (!ghImport.available) {
823
+ return c.json(
824
+ {
825
+ error: ghImport.reason || 'GitHub CLI is not available',
826
+ },
827
+ 400,
828
+ );
829
+ }
830
+ if (!ghImport.authenticated) {
831
+ return c.json(
832
+ {
833
+ error: ghImport.reason || 'GitHub CLI is not authenticated',
834
+ },
835
+ 400,
836
+ );
837
+ }
838
+
839
+ const ghToken = execFileSync('gh', ['auth', 'token'], {
840
+ encoding: 'utf8',
841
+ stdio: ['ignore', 'pipe', 'pipe'],
842
+ }).trim();
843
+ if (!ghToken) {
844
+ return c.json({ error: 'GitHub CLI returned an empty token' }, 400);
845
+ }
846
+
847
+ const modelsResult = await fetchCopilotModels(ghToken);
848
+ if (!modelsResult.ok) {
849
+ return c.json(
850
+ {
851
+ error: `Imported gh token is not valid for Copilot: ${modelsResult.message}`,
852
+ },
853
+ 400,
854
+ );
855
+ }
856
+
857
+ await setAuth(
858
+ 'copilot',
859
+ {
860
+ type: 'oauth',
861
+ refresh: ghToken,
862
+ access: ghToken,
863
+ expires: 0,
864
+ },
865
+ undefined,
866
+ 'global',
867
+ );
868
+
869
+ const models = Array.from(modelsResult.models).sort();
870
+ return c.json({
871
+ success: true,
872
+ provider: 'copilot',
873
+ source: 'gh',
874
+ modelCount: models.length,
875
+ hasGpt52Codex: modelsResult.models.has('gpt-5.2-codex'),
876
+ sampleModels: models.slice(0, 25),
877
+ });
878
+ } catch (error) {
879
+ const message =
880
+ error instanceof Error
881
+ ? error.message
882
+ : 'Failed to import GitHub CLI token';
883
+ logger.error('Failed to import Copilot token from GitHub CLI', error);
884
+ return c.json({ error: message }, 500);
885
+ }
886
+ });
887
+
888
+ app.get('/v1/auth/copilot/diagnostics', async (c) => {
889
+ try {
890
+ const projectRoot = process.cwd();
891
+ const entries: Array<{
892
+ source: 'env' | 'stored';
893
+ configured: boolean;
894
+ modelCount?: number;
895
+ hasGpt52Codex?: boolean;
896
+ sampleModels?: string[];
897
+ restrictedByOrgPolicy?: boolean;
898
+ restrictedOrg?: string;
899
+ restrictionMessage?: string;
900
+ error?: string;
901
+ }> = [];
902
+
903
+ const envToken = readEnvKey('copilot');
904
+ if (envToken) {
905
+ const modelsResult = await fetchCopilotModels(envToken);
906
+ if (modelsResult.ok) {
907
+ const models = Array.from(modelsResult.models).sort();
908
+ entries.push({
909
+ source: 'env',
910
+ configured: true,
911
+ modelCount: models.length,
912
+ hasGpt52Codex: modelsResult.models.has('gpt-5.2-codex'),
913
+ sampleModels: models.slice(0, 25),
914
+ });
915
+ } else {
916
+ entries.push({
917
+ source: 'env',
918
+ configured: true,
919
+ error: modelsResult.message,
920
+ });
921
+ }
922
+ } else {
923
+ entries.push({ source: 'env', configured: false });
924
+ }
925
+
926
+ const storedAuth = await getAuth('copilot', projectRoot);
927
+ if (storedAuth?.type === 'oauth') {
928
+ const modelsResult = await fetchCopilotModels(storedAuth.refresh);
929
+ const restriction = await detectOAuthOrgRestriction(storedAuth.refresh);
930
+ if (modelsResult.ok) {
931
+ const models = Array.from(modelsResult.models).sort();
932
+ entries.push({
933
+ source: 'stored',
934
+ configured: true,
935
+ modelCount: models.length,
936
+ hasGpt52Codex: modelsResult.models.has('gpt-5.2-codex'),
937
+ sampleModels: models.slice(0, 25),
938
+ restrictedByOrgPolicy: restriction.restricted,
939
+ restrictedOrg: restriction.org,
940
+ restrictionMessage: restriction.message,
941
+ });
942
+ } else {
943
+ entries.push({
944
+ source: 'stored',
945
+ configured: true,
946
+ error: modelsResult.message,
947
+ restrictedByOrgPolicy: restriction.restricted,
948
+ restrictedOrg: restriction.org,
949
+ restrictionMessage: restriction.message,
950
+ });
951
+ }
952
+ } else {
953
+ entries.push({ source: 'stored', configured: false });
954
+ }
955
+
956
+ return c.json({
957
+ tokenSources: entries,
958
+ methods: {
959
+ oauth: true,
960
+ token: true,
961
+ ghImport: getGhImportCapability(),
962
+ },
963
+ });
964
+ } catch (error) {
965
+ const message =
966
+ error instanceof Error ? error.message : 'Failed to inspect Copilot';
967
+ logger.error('Failed to build Copilot diagnostics', error);
968
+ return c.json({ error: message }, 500);
969
+ }
970
+ });
971
+
588
972
  app.post('/v1/auth/onboarding/complete', async (c) => {
589
973
  try {
590
974
  await setOnboardingComplete();
@@ -8,9 +8,11 @@ import { discoverAllAgents, getDefault } from './utils.ts';
8
8
  export function registerAgentsRoute(app: Hono) {
9
9
  app.get('/v1/config/agents', async (c) => {
10
10
  try {
11
- const embeddedConfig = c.get('embeddedConfig') as
12
- | EmbeddedAppConfig
13
- | undefined;
11
+ const embeddedConfig = (
12
+ c as unknown as {
13
+ get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
14
+ }
15
+ ).get('embeddedConfig');
14
16
 
15
17
  if (embeddedConfig) {
16
18
  const agents = embeddedConfig.agents
@@ -1,5 +1,5 @@
1
1
  import type { Hono } from 'hono';
2
- import { setConfig, loadConfig } from '@ottocode/sdk';
2
+ import { setConfig, loadConfig, type ProviderId } from '@ottocode/sdk';
3
3
  import { logger } from '@ottocode/sdk';
4
4
  import { serializeError } from '../../runtime/errors/api-error.ts';
5
5
 
@@ -21,7 +21,7 @@ export function registerDefaultsRoute(app: Hono) {
21
21
  const scope = body.scope || 'global';
22
22
  const updates: Partial<{
23
23
  agent: string;
24
- provider: string;
24
+ provider: ProviderId;
25
25
  model: string;
26
26
  toolApproval: 'auto' | 'dangerous' | 'all';
27
27
  guidedMode: boolean;
@@ -30,7 +30,7 @@ export function registerDefaultsRoute(app: Hono) {
30
30
  }> = {};
31
31
 
32
32
  if (body.agent) updates.agent = body.agent;
33
- if (body.provider) updates.provider = body.provider;
33
+ if (body.provider) updates.provider = body.provider as ProviderId;
34
34
  if (body.model) updates.model = body.model;
35
35
  if (body.toolApproval) updates.toolApproval = body.toolApproval;
36
36
  if (body.guidedMode !== undefined) updates.guidedMode = body.guidedMode;
@@ -13,9 +13,11 @@ export function registerMainConfigRoute(app: Hono) {
13
13
  app.get('/v1/config', async (c) => {
14
14
  try {
15
15
  const projectRoot = c.req.query('project') || process.cwd();
16
- const embeddedConfig = c.get('embeddedConfig') as
17
- | EmbeddedAppConfig
18
- | undefined;
16
+ const embeddedConfig = (
17
+ c as unknown as {
18
+ get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
19
+ }
20
+ ).get('embeddedConfig');
19
21
 
20
22
  const cfg = await loadConfig(projectRoot);
21
23
 
@@ -2,11 +2,13 @@ import type { Hono } from 'hono';
2
2
  import {
3
3
  loadConfig,
4
4
  catalog,
5
+ getAuth,
6
+ logger,
7
+ readEnvKey,
5
8
  type ProviderId,
6
9
  filterModelsForAuthType,
7
10
  } from '@ottocode/sdk';
8
11
  import type { EmbeddedAppConfig } from '../../index.ts';
9
- import { logger } from '@ottocode/sdk';
10
12
  import { serializeError } from '../../runtime/errors/api-error.ts';
11
13
  import {
12
14
  isProviderAuthorizedHybrid,
@@ -15,12 +17,76 @@ import {
15
17
  getAuthTypeForProvider,
16
18
  } from './utils.ts';
17
19
 
20
+ const COPILOT_MODELS_URL = 'https://api.githubcopilot.com/models';
21
+
22
+ function filterCopilotAvailability<T extends { id: string }>(
23
+ provider: ProviderId,
24
+ models: T[],
25
+ copilotAllowedModels: Set<string> | null,
26
+ ): T[] {
27
+ if (provider !== 'copilot') return models;
28
+ if (!copilotAllowedModels || copilotAllowedModels.size === 0) return models;
29
+ return models.filter((m) => copilotAllowedModels.has(m.id));
30
+ }
31
+
32
+ async function getCopilotAuthTokens(projectRoot: string): Promise<string[]> {
33
+ const tokens: string[] = [];
34
+
35
+ const envToken = readEnvKey('copilot');
36
+ if (envToken) tokens.push(envToken);
37
+
38
+ const auth = await getAuth('copilot', projectRoot);
39
+ if (auth?.type === 'oauth' && auth.refresh) {
40
+ if (auth.refresh !== envToken) {
41
+ tokens.push(auth.refresh);
42
+ }
43
+ }
44
+
45
+ return tokens;
46
+ }
47
+
48
+ async function getAuthorizedCopilotModels(
49
+ projectRoot: string,
50
+ ): Promise<Set<string> | null> {
51
+ const tokens = await getCopilotAuthTokens(projectRoot);
52
+ if (!tokens.length) return null;
53
+
54
+ const merged = new Set<string>();
55
+ let successful = false;
56
+
57
+ for (const token of tokens) {
58
+ try {
59
+ const response = await fetch(COPILOT_MODELS_URL, {
60
+ headers: {
61
+ Authorization: `Bearer ${token}`,
62
+ 'Openai-Intent': 'conversation-edits',
63
+ 'User-Agent': 'ottocode',
64
+ },
65
+ });
66
+ if (!response.ok) continue;
67
+
68
+ successful = true;
69
+ const payload = (await response.json()) as {
70
+ data?: Array<{ id?: string }>;
71
+ };
72
+
73
+ for (const id of (payload.data ?? []).map((item) => item.id)) {
74
+ if (id) merged.add(id);
75
+ }
76
+ } catch {}
77
+ }
78
+
79
+ return successful ? merged : null;
80
+ }
81
+
18
82
  export function registerModelsRoutes(app: Hono) {
19
83
  app.get('/v1/config/providers/:provider/models', async (c) => {
20
84
  try {
21
- const embeddedConfig = c.get('embeddedConfig') as
22
- | EmbeddedAppConfig
23
- | undefined;
85
+ const embeddedConfig = (
86
+ c as unknown as {
87
+ get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
88
+ }
89
+ ).get('embeddedConfig');
24
90
  const provider = c.req.param('provider') as ProviderId;
25
91
 
26
92
  const projectRoot = c.req.query('project') || process.cwd();
@@ -53,9 +119,19 @@ export function registerModelsRoutes(app: Hono) {
53
119
  providerCatalog.models,
54
120
  authType,
55
121
  );
122
+ const copilotAllowedModels =
123
+ provider === 'copilot'
124
+ ? await getAuthorizedCopilotModels(projectRoot)
125
+ : null;
126
+
127
+ const availableModels = filterCopilotAvailability(
128
+ provider,
129
+ filteredModels,
130
+ copilotAllowedModels,
131
+ );
56
132
 
57
133
  return c.json({
58
- models: filteredModels.map((m) => ({
134
+ models: availableModels.map((m) => ({
59
135
  id: m.id,
60
136
  label: m.label || m.id,
61
137
  toolCall: m.toolCall,
@@ -78,9 +154,11 @@ export function registerModelsRoutes(app: Hono) {
78
154
 
79
155
  app.get('/v1/config/models', async (c) => {
80
156
  try {
81
- const embeddedConfig = c.get('embeddedConfig') as
82
- | EmbeddedAppConfig
83
- | undefined;
157
+ const embeddedConfig = (
158
+ c as unknown as {
159
+ get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
160
+ }
161
+ ).get('embeddedConfig');
84
162
 
85
163
  const projectRoot = c.req.query('project') || process.cwd();
86
164
  const cfg = await loadConfig(projectRoot);
@@ -94,6 +172,7 @@ export function registerModelsRoutes(app: Hono) {
94
172
  string,
95
173
  {
96
174
  label: string;
175
+ authType?: 'api' | 'oauth' | 'wallet';
97
176
  models: Array<{
98
177
  id: string;
99
178
  label: string;
@@ -9,14 +9,18 @@ import { getAuthorizedProviders, getDefault } from './utils.ts';
9
9
  export function registerProvidersRoute(app: Hono) {
10
10
  app.get('/v1/config/providers', async (c) => {
11
11
  try {
12
- const embeddedConfig = c.get('embeddedConfig') as
13
- | EmbeddedAppConfig
14
- | undefined;
12
+ const embeddedConfig = (
13
+ c as unknown as {
14
+ get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
15
+ }
16
+ ).get('embeddedConfig');
15
17
 
16
18
  if (embeddedConfig) {
17
19
  const providers = embeddedConfig.auth
18
20
  ? (Object.keys(embeddedConfig.auth) as ProviderId[])
19
- : [embeddedConfig.provider];
21
+ : embeddedConfig.provider
22
+ ? [embeddedConfig.provider]
23
+ : [];
20
24
 
21
25
  return c.json({
22
26
  providers,