ai-agent-router 0.1.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.
Files changed (166) hide show
  1. package/.claude/commands/openspec/apply.md +23 -0
  2. package/.claude/commands/openspec/archive.md +27 -0
  3. package/.claude/commands/openspec/proposal.md +28 -0
  4. package/.claude/settings.local.json +12 -0
  5. package/.claude/skills/ui-ux-pro-max/SKILL.md +228 -0
  6. package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  7. package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  8. package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  9. package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  10. package/.claude/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  11. package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  12. package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  13. package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  14. package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  15. package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  16. package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  17. package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  18. package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  19. package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  20. package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  21. package/.claude/skills/ui-ux-pro-max/data/styles.csv +59 -0
  22. package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  23. package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-311.pyc +0 -0
  25. package/.claude/skills/ui-ux-pro-max/scripts/core.py +238 -0
  26. package/.claude/skills/ui-ux-pro-max/scripts/search.py +61 -0
  27. package/.cursor/commands/openspec-apply.md +23 -0
  28. package/.cursor/commands/openspec-archive.md +27 -0
  29. package/.cursor/commands/openspec-proposal.md +28 -0
  30. package/.cursor/commands/ui-ux-pro-max.md +226 -0
  31. package/.eslintrc.json +3 -0
  32. package/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  33. package/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  34. package/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  35. package/.shared/ui-ux-pro-max/data/products.csv +97 -0
  36. package/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  37. package/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  38. package/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  39. package/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  40. package/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  41. package/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  42. package/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  43. package/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  44. package/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  45. package/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  46. package/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  47. package/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  48. package/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  49. package/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  50. package/.shared/ui-ux-pro-max/scripts/core.py +238 -0
  51. package/.shared/ui-ux-pro-max/scripts/search.py +61 -0
  52. package/AGENTS.md +18 -0
  53. package/CLAUDE.md +18 -0
  54. package/IMPLEMENTATION.md +157 -0
  55. package/LICENSE +21 -0
  56. package/README.md +165 -0
  57. package/dist/.next/types/app/api/config/route.js +52 -0
  58. package/dist/.next/types/app/api/gateway/[...path]/route.js +52 -0
  59. package/dist/.next/types/app/api/gateway/route.js +52 -0
  60. package/dist/.next/types/app/api/logs/route.js +52 -0
  61. package/dist/.next/types/app/api/models/route.js +52 -0
  62. package/dist/.next/types/app/api/providers/route.js +52 -0
  63. package/dist/.next/types/app/api/providers/test/route.js +52 -0
  64. package/dist/.next/types/app/api/service/start/route.js +52 -0
  65. package/dist/.next/types/app/api/service/status/route.js +52 -0
  66. package/dist/.next/types/app/api/service/stop/route.js +52 -0
  67. package/dist/.next/types/app/layout.js +22 -0
  68. package/dist/.next/types/app/logs/page.js +22 -0
  69. package/dist/.next/types/app/models/page.js +22 -0
  70. package/dist/.next/types/app/page.js +22 -0
  71. package/dist/.next/types/app/providers/page.js +22 -0
  72. package/dist/src/app/api/config/route.js +43 -0
  73. package/dist/src/app/api/gateway/[...path]/route.js +83 -0
  74. package/dist/src/app/api/gateway/route.js +63 -0
  75. package/dist/src/app/api/logs/route.js +34 -0
  76. package/dist/src/app/api/models/route.js +152 -0
  77. package/dist/src/app/api/providers/route.js +118 -0
  78. package/dist/src/app/api/providers/test/route.js +154 -0
  79. package/dist/src/app/api/service/start/route.js +55 -0
  80. package/dist/src/app/api/service/status/route.js +17 -0
  81. package/dist/src/app/api/service/stop/route.js +20 -0
  82. package/dist/src/app/components/ConfirmDialog.jsx +31 -0
  83. package/dist/src/app/components/Nav.jsx +45 -0
  84. package/dist/src/app/components/Toast.jsx +37 -0
  85. package/dist/src/app/components/ToastProvider.jsx +21 -0
  86. package/dist/src/app/layout.jsx +13 -0
  87. package/dist/src/app/logs/page.jsx +210 -0
  88. package/dist/src/app/models/page.jsx +291 -0
  89. package/dist/src/app/page.jsx +236 -0
  90. package/dist/src/app/providers/page.jsx +402 -0
  91. package/dist/src/cli/index.js +90 -0
  92. package/dist/src/db/database.js +69 -0
  93. package/dist/src/db/queries.js +261 -0
  94. package/dist/src/db/schema.js +67 -0
  95. package/dist/src/server/crypto.js +22 -0
  96. package/dist/src/server/gateway-server.js +200 -0
  97. package/dist/src/server/gateway.js +76 -0
  98. package/dist/src/server/logger.js +72 -0
  99. package/dist/src/server/providers/anthropic.js +52 -0
  100. package/dist/src/server/providers/gemini.js +64 -0
  101. package/dist/src/server/providers/index.js +16 -0
  102. package/dist/src/server/providers/openai.js +86 -0
  103. package/dist/src/server/providers/types.js +1 -0
  104. package/dist/src/server/service-manager.js +286 -0
  105. package/docs/TODO.md +19 -0
  106. package/next.config.js +7 -0
  107. package/openspec/AGENTS.md +456 -0
  108. package/openspec/changes/add-logging/proposal.md +18 -0
  109. package/openspec/changes/add-logging/specs/core/spec.md +21 -0
  110. package/openspec/changes/add-logging/tasks.md +16 -0
  111. package/openspec/changes/add-provider-test-connection/proposal.md +22 -0
  112. package/openspec/changes/add-provider-test-connection/specs/model-provider/spec.md +68 -0
  113. package/openspec/changes/add-provider-test-connection/tasks.md +31 -0
  114. package/openspec/changes/improve-gateway-startup/design.md +137 -0
  115. package/openspec/changes/improve-gateway-startup/proposal.md +33 -0
  116. package/openspec/changes/improve-gateway-startup/specs/api-gateway/spec.md +94 -0
  117. package/openspec/changes/improve-gateway-startup/specs/web-ui/spec.md +67 -0
  118. package/openspec/changes/improve-gateway-startup/tasks.md +47 -0
  119. package/openspec/changes/init-api-gateway/design.md +185 -0
  120. package/openspec/changes/init-api-gateway/proposal.md +30 -0
  121. package/openspec/changes/init-api-gateway/specs/api-gateway/spec.md +42 -0
  122. package/openspec/changes/init-api-gateway/specs/cli-tool/spec.md +40 -0
  123. package/openspec/changes/init-api-gateway/specs/model-management/spec.md +47 -0
  124. package/openspec/changes/init-api-gateway/specs/model-provider/spec.md +33 -0
  125. package/openspec/changes/init-api-gateway/specs/request-logging/spec.md +54 -0
  126. package/openspec/changes/init-api-gateway/specs/web-ui/spec.md +49 -0
  127. package/openspec/changes/init-api-gateway/tasks.md +84 -0
  128. package/openspec/project.md +58 -0
  129. package/package.json +51 -0
  130. package/postcss.config.js +6 -0
  131. package/src/app/api/config/route.ts +62 -0
  132. package/src/app/api/gateway/[...path]/route.ts +118 -0
  133. package/src/app/api/gateway/route.ts +77 -0
  134. package/src/app/api/logs/route.ts +48 -0
  135. package/src/app/api/models/route.ts +210 -0
  136. package/src/app/api/providers/route.ts +162 -0
  137. package/src/app/api/providers/test/route.ts +182 -0
  138. package/src/app/api/service/start/route.ts +73 -0
  139. package/src/app/api/service/status/route.ts +22 -0
  140. package/src/app/api/service/stop/route.ts +27 -0
  141. package/src/app/components/ConfirmDialog.tsx +63 -0
  142. package/src/app/components/Nav.tsx +66 -0
  143. package/src/app/components/Toast.tsx +61 -0
  144. package/src/app/components/ToastProvider.tsx +43 -0
  145. package/src/app/globals.css +71 -0
  146. package/src/app/layout.tsx +22 -0
  147. package/src/app/logs/page.tsx +261 -0
  148. package/src/app/models/page.tsx +500 -0
  149. package/src/app/page.tsx +742 -0
  150. package/src/app/providers/page.tsx +558 -0
  151. package/src/cli/index.ts +95 -0
  152. package/src/db/database.ts +125 -0
  153. package/src/db/queries.ts +339 -0
  154. package/src/db/schema.ts +117 -0
  155. package/src/server/crypto.ts +48 -0
  156. package/src/server/gateway-server.ts +306 -0
  157. package/src/server/gateway.ts +163 -0
  158. package/src/server/logger.ts +96 -0
  159. package/src/server/providers/anthropic.ts +121 -0
  160. package/src/server/providers/gemini.ts +112 -0
  161. package/src/server/providers/index.ts +20 -0
  162. package/src/server/providers/openai.ts +235 -0
  163. package/src/server/providers/types.ts +20 -0
  164. package/src/server/service-manager.ts +321 -0
  165. package/tailwind.config.js +16 -0
  166. package/tsconfig.json +29 -0
@@ -0,0 +1,22 @@
1
+ // File: /Users/pluckypan/own/AI/ai-agent-router/src/app/layout.tsx
2
+ import * as entry from '../../../src/app/layout.js';
3
+ // Check that the entry is a valid entry
4
+ checkFields();
5
+ // Check the prop type of the entry function
6
+ checkFields();
7
+ // Check the arguments and return type of the generateMetadata function
8
+ if ('generateMetadata' in entry) {
9
+ checkFields();
10
+ checkFields();
11
+ }
12
+ // Check the arguments and return type of the generateViewport function
13
+ if ('generateViewport' in entry) {
14
+ checkFields();
15
+ checkFields();
16
+ }
17
+ // Check the arguments and return type of the generateStaticParams function
18
+ if ('generateStaticParams' in entry) {
19
+ checkFields();
20
+ checkFields();
21
+ }
22
+ function checkFields() { }
@@ -0,0 +1,22 @@
1
+ // File: /Users/pluckypan/own/AI/ai-agent-router/src/app/logs/page.tsx
2
+ import * as entry from '../../../../src/app/logs/page.js';
3
+ // Check that the entry is a valid entry
4
+ checkFields();
5
+ // Check the prop type of the entry function
6
+ checkFields();
7
+ // Check the arguments and return type of the generateMetadata function
8
+ if ('generateMetadata' in entry) {
9
+ checkFields();
10
+ checkFields();
11
+ }
12
+ // Check the arguments and return type of the generateViewport function
13
+ if ('generateViewport' in entry) {
14
+ checkFields();
15
+ checkFields();
16
+ }
17
+ // Check the arguments and return type of the generateStaticParams function
18
+ if ('generateStaticParams' in entry) {
19
+ checkFields();
20
+ checkFields();
21
+ }
22
+ function checkFields() { }
@@ -0,0 +1,22 @@
1
+ // File: /Users/pluckypan/own/AI/ai-agent-router/src/app/models/page.tsx
2
+ import * as entry from '../../../../src/app/models/page.js';
3
+ // Check that the entry is a valid entry
4
+ checkFields();
5
+ // Check the prop type of the entry function
6
+ checkFields();
7
+ // Check the arguments and return type of the generateMetadata function
8
+ if ('generateMetadata' in entry) {
9
+ checkFields();
10
+ checkFields();
11
+ }
12
+ // Check the arguments and return type of the generateViewport function
13
+ if ('generateViewport' in entry) {
14
+ checkFields();
15
+ checkFields();
16
+ }
17
+ // Check the arguments and return type of the generateStaticParams function
18
+ if ('generateStaticParams' in entry) {
19
+ checkFields();
20
+ checkFields();
21
+ }
22
+ function checkFields() { }
@@ -0,0 +1,22 @@
1
+ // File: /Users/pluckypan/own/AI/ai-agent-router/src/app/page.tsx
2
+ import * as entry from '../../../src/app/page.js';
3
+ // Check that the entry is a valid entry
4
+ checkFields();
5
+ // Check the prop type of the entry function
6
+ checkFields();
7
+ // Check the arguments and return type of the generateMetadata function
8
+ if ('generateMetadata' in entry) {
9
+ checkFields();
10
+ checkFields();
11
+ }
12
+ // Check the arguments and return type of the generateViewport function
13
+ if ('generateViewport' in entry) {
14
+ checkFields();
15
+ checkFields();
16
+ }
17
+ // Check the arguments and return type of the generateStaticParams function
18
+ if ('generateStaticParams' in entry) {
19
+ checkFields();
20
+ checkFields();
21
+ }
22
+ function checkFields() { }
@@ -0,0 +1,22 @@
1
+ // File: /Users/pluckypan/own/AI/ai-agent-router/src/app/providers/page.tsx
2
+ import * as entry from '../../../../src/app/providers/page.js';
3
+ // Check that the entry is a valid entry
4
+ checkFields();
5
+ // Check the prop type of the entry function
6
+ checkFields();
7
+ // Check the arguments and return type of the generateMetadata function
8
+ if ('generateMetadata' in entry) {
9
+ checkFields();
10
+ checkFields();
11
+ }
12
+ // Check the arguments and return type of the generateViewport function
13
+ if ('generateViewport' in entry) {
14
+ checkFields();
15
+ checkFields();
16
+ }
17
+ // Check the arguments and return type of the generateStaticParams function
18
+ if ('generateStaticParams' in entry) {
19
+ checkFields();
20
+ checkFields();
21
+ }
22
+ function checkFields() { }
@@ -0,0 +1,43 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getDatabase } from '@/db/database';
3
+ import { getConfig, setConfig, getAllConfig } from '@/db/queries';
4
+ // Ensure Node.js runtime (required for SQLite)
5
+ export const runtime = 'nodejs';
6
+ export async function GET(request) {
7
+ try {
8
+ // Initialize database on request (safer than module load)
9
+ getDatabase();
10
+ const { searchParams } = new URL(request.url);
11
+ const key = searchParams.get('key');
12
+ if (key) {
13
+ const config = getConfig(key);
14
+ if (!config) {
15
+ return NextResponse.json({ error: 'Config not found' }, { status: 404 });
16
+ }
17
+ return NextResponse.json(config);
18
+ }
19
+ const configs = getAllConfig();
20
+ return NextResponse.json(configs);
21
+ }
22
+ catch (error) {
23
+ console.error('Config API error:', error);
24
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
25
+ }
26
+ }
27
+ export async function POST(request) {
28
+ try {
29
+ // Initialize database on request
30
+ getDatabase();
31
+ const body = await request.json();
32
+ const { key, value } = body;
33
+ if (!key || value === undefined) {
34
+ return NextResponse.json({ error: 'Key and value are required' }, { status: 400 });
35
+ }
36
+ const config = setConfig(key, typeof value === 'string' ? value : JSON.stringify(value));
37
+ return NextResponse.json(config);
38
+ }
39
+ catch (error) {
40
+ console.error('Config POST API error:', error);
41
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
42
+ }
43
+ }
@@ -0,0 +1,83 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getDatabase } from '@/db/database';
3
+ import { handleGatewayRequest } from '@/server/gateway';
4
+ // Ensure Node.js runtime (required for SQLite)
5
+ export const runtime = 'nodejs';
6
+ export async function POST(request, { params }) {
7
+ return handleRequest(request, params, 'POST');
8
+ }
9
+ export async function GET(request, { params }) {
10
+ return handleRequest(request, params, 'GET');
11
+ }
12
+ export async function PUT(request, { params }) {
13
+ return handleRequest(request, params, 'PUT');
14
+ }
15
+ export async function PATCH(request, { params }) {
16
+ return handleRequest(request, params, 'PATCH');
17
+ }
18
+ export async function DELETE(request, { params }) {
19
+ return handleRequest(request, params, 'DELETE');
20
+ }
21
+ async function handleRequest(request, params, method) {
22
+ try {
23
+ getDatabase();
24
+ // Extract model ID from path or query
25
+ // Common patterns: /v1/models/{model_id}/... or ?model=...
26
+ const path = params.path.join('/');
27
+ const searchParams = request.nextUrl.searchParams;
28
+ // Try to get model from query or path
29
+ let modelId = searchParams.get('model') || searchParams.get('model_id');
30
+ // If not in query, try to extract from path (e.g., /v1/models/gpt-4/completions)
31
+ if (!modelId) {
32
+ const pathParts = path.split('/');
33
+ const modelsIndex = pathParts.indexOf('models');
34
+ if (modelsIndex >= 0 && pathParts[modelsIndex + 1]) {
35
+ modelId = pathParts[modelsIndex + 1];
36
+ }
37
+ }
38
+ // If still no model, try to get from body
39
+ if (!modelId) {
40
+ try {
41
+ const body = await request.json();
42
+ modelId = body.model || body.model_id;
43
+ }
44
+ catch {
45
+ // Body might not be JSON or might be empty
46
+ }
47
+ }
48
+ if (!modelId) {
49
+ return NextResponse.json({ error: { message: 'Model ID not specified' } }, { status: 400 });
50
+ }
51
+ // Get request body
52
+ let body = null;
53
+ try {
54
+ if (method !== 'GET' && method !== 'HEAD') {
55
+ const text = await request.text();
56
+ if (text) {
57
+ body = JSON.parse(text);
58
+ }
59
+ }
60
+ }
61
+ catch {
62
+ // Body might not be JSON
63
+ }
64
+ // Build gateway request
65
+ const gatewayRequest = {
66
+ method,
67
+ path: '/' + path,
68
+ headers: Object.fromEntries(request.headers.entries()),
69
+ query: Object.fromEntries(searchParams.entries()),
70
+ body,
71
+ };
72
+ // Handle the request
73
+ const response = await handleGatewayRequest(modelId, gatewayRequest);
74
+ // Return response
75
+ return NextResponse.json(response.body, {
76
+ status: response.status,
77
+ headers: response.headers,
78
+ });
79
+ }
80
+ catch (error) {
81
+ return NextResponse.json({ error: { message: error.message || 'Internal server error' } }, { status: 500 });
82
+ }
83
+ }
@@ -0,0 +1,63 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getDatabase } from '@/db/database';
3
+ import { handleGatewayRequest } from '@/server/gateway';
4
+ // Ensure Node.js runtime (required for SQLite)
5
+ export const runtime = 'nodejs';
6
+ // Handle requests to /api/gateway (root gateway endpoint)
7
+ export async function POST(request) {
8
+ return handleGatewayRequestDirect(request, 'POST');
9
+ }
10
+ export async function GET(request) {
11
+ return handleGatewayRequestDirect(request, 'GET');
12
+ }
13
+ async function handleGatewayRequestDirect(request, method) {
14
+ try {
15
+ getDatabase();
16
+ const searchParams = request.nextUrl.searchParams;
17
+ let modelId = searchParams.get('model') || searchParams.get('model_id');
18
+ // Try to get from body
19
+ if (!modelId) {
20
+ try {
21
+ const body = await request.json();
22
+ modelId = body.model || body.model_id;
23
+ }
24
+ catch {
25
+ // Body might not be JSON
26
+ }
27
+ }
28
+ if (!modelId) {
29
+ return NextResponse.json({ error: { message: 'Model ID not specified' } }, { status: 400 });
30
+ }
31
+ // Get request body
32
+ let body = null;
33
+ try {
34
+ if (method !== 'GET' && method !== 'HEAD') {
35
+ const text = await request.text();
36
+ if (text) {
37
+ body = JSON.parse(text);
38
+ }
39
+ }
40
+ }
41
+ catch {
42
+ // Body might not be JSON
43
+ }
44
+ // Build gateway request
45
+ const gatewayRequest = {
46
+ method,
47
+ path: '/',
48
+ headers: Object.fromEntries(request.headers.entries()),
49
+ query: Object.fromEntries(searchParams.entries()),
50
+ body,
51
+ };
52
+ // Handle the request
53
+ const response = await handleGatewayRequest(modelId, gatewayRequest);
54
+ // Return response
55
+ return NextResponse.json(response.body, {
56
+ status: response.status,
57
+ headers: response.headers,
58
+ });
59
+ }
60
+ catch (error) {
61
+ return NextResponse.json({ error: { message: error.message || 'Internal server error' } }, { status: 500 });
62
+ }
63
+ }
@@ -0,0 +1,34 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getDatabase } from '@/db/database';
3
+ import { getRequestLogs, getRequestLogById, getRequestLogCount } from '@/db/queries';
4
+ // Ensure Node.js runtime (required for SQLite)
5
+ export const runtime = 'nodejs';
6
+ export async function GET(request) {
7
+ try {
8
+ getDatabase();
9
+ const { searchParams } = new URL(request.url);
10
+ const id = searchParams.get('id');
11
+ const limit = parseInt(searchParams.get('limit') || '100');
12
+ const offset = parseInt(searchParams.get('offset') || '0');
13
+ const modelId = searchParams.get('model_id');
14
+ if (id) {
15
+ const log = getRequestLogById(parseInt(id));
16
+ if (!log) {
17
+ return NextResponse.json({ error: 'Log not found' }, { status: 404 });
18
+ }
19
+ return NextResponse.json(log);
20
+ }
21
+ const logs = getRequestLogs(limit, offset, modelId ? parseInt(modelId) : undefined);
22
+ const total = getRequestLogCount(modelId ? parseInt(modelId) : undefined);
23
+ return NextResponse.json({
24
+ logs,
25
+ total,
26
+ limit,
27
+ offset,
28
+ });
29
+ }
30
+ catch (error) {
31
+ console.error('Logs API error:', error);
32
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
33
+ }
34
+ }
@@ -0,0 +1,152 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getDatabase } from '@/db/database';
3
+ import { getAllModels, createModel, updateModel, deleteModel, getModelsByProvider, getProviderById, } from '@/db/queries';
4
+ import { getProviderAdapter } from '@/server/providers';
5
+ // Ensure Node.js runtime (required for SQLite)
6
+ export const runtime = 'nodejs';
7
+ export async function GET(request) {
8
+ try {
9
+ getDatabase();
10
+ const { searchParams } = new URL(request.url);
11
+ const providerId = searchParams.get('provider_id');
12
+ if (providerId) {
13
+ const models = getModelsByProvider(parseInt(providerId));
14
+ return NextResponse.json(models);
15
+ }
16
+ else {
17
+ const models = getAllModels();
18
+ return NextResponse.json(models);
19
+ }
20
+ }
21
+ catch (error) {
22
+ console.error('Models API error:', error);
23
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
24
+ }
25
+ }
26
+ export async function POST(request) {
27
+ try {
28
+ getDatabase();
29
+ const body = await request.json();
30
+ const { provider_id, name, model_id, enabled } = body;
31
+ if (!provider_id || !name || !model_id) {
32
+ return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
33
+ }
34
+ const model = createModel({
35
+ provider_id,
36
+ name,
37
+ model_id,
38
+ enabled: enabled !== undefined ? enabled : true,
39
+ });
40
+ return NextResponse.json(model);
41
+ }
42
+ catch (error) {
43
+ console.error('Models API error:', error);
44
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
45
+ }
46
+ }
47
+ export async function PUT(request) {
48
+ try {
49
+ getDatabase();
50
+ const body = await request.json();
51
+ const { id, name, model_id, enabled, provider_id } = body;
52
+ if (!id) {
53
+ return NextResponse.json({ error: 'Model ID is required' }, { status: 400 });
54
+ }
55
+ const updateData = {};
56
+ if (name !== undefined)
57
+ updateData.name = name;
58
+ if (model_id !== undefined)
59
+ updateData.model_id = model_id;
60
+ if (enabled !== undefined)
61
+ updateData.enabled = enabled;
62
+ if (provider_id !== undefined)
63
+ updateData.provider_id = provider_id;
64
+ const model = updateModel(id, updateData);
65
+ if (!model) {
66
+ return NextResponse.json({ error: 'Model not found' }, { status: 404 });
67
+ }
68
+ return NextResponse.json(model);
69
+ }
70
+ catch (error) {
71
+ console.error('Models API error:', error);
72
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
73
+ }
74
+ }
75
+ export async function DELETE(request) {
76
+ try {
77
+ getDatabase();
78
+ const { searchParams } = new URL(request.url);
79
+ const id = searchParams.get('id');
80
+ if (!id) {
81
+ return NextResponse.json({ error: 'Model ID is required' }, { status: 400 });
82
+ }
83
+ const success = deleteModel(parseInt(id));
84
+ if (!success) {
85
+ return NextResponse.json({ error: 'Model not found' }, { status: 404 });
86
+ }
87
+ return NextResponse.json({ success: true });
88
+ }
89
+ catch (error) {
90
+ console.error('Models API error:', error);
91
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
92
+ }
93
+ }
94
+ // Fetch models from provider
95
+ export async function PATCH(request) {
96
+ try {
97
+ getDatabase();
98
+ const body = await request.json();
99
+ const { provider_id } = body;
100
+ if (!provider_id) {
101
+ return NextResponse.json({ error: 'Provider ID is required' }, { status: 400 });
102
+ }
103
+ const provider = getProviderById(provider_id);
104
+ if (!provider) {
105
+ return NextResponse.json({ error: 'Provider not found' }, { status: 404 });
106
+ }
107
+ console.log('[Models API] Fetching models for provider:', {
108
+ providerId: provider_id,
109
+ providerName: provider.name,
110
+ protocol: provider.protocol,
111
+ baseUrl: provider.base_url,
112
+ });
113
+ const adapter = getProviderAdapter(provider.protocol);
114
+ const models = await adapter.listModels(provider);
115
+ console.log('[Models API] Fetched models:', models.length);
116
+ // Create or update models
117
+ const results = [];
118
+ for (const modelInfo of models) {
119
+ try {
120
+ const existing = getAllModels().find(m => m.provider_id === provider_id && m.model_id === modelInfo.id);
121
+ if (existing) {
122
+ updateModel(existing.id, {
123
+ name: modelInfo.name,
124
+ model_id: modelInfo.id,
125
+ });
126
+ results.push({ ...existing, name: modelInfo.name });
127
+ }
128
+ else {
129
+ const newModel = createModel({
130
+ provider_id,
131
+ name: modelInfo.name,
132
+ model_id: modelInfo.id,
133
+ enabled: false, // 默认关闭
134
+ });
135
+ results.push(newModel);
136
+ }
137
+ }
138
+ catch (error) {
139
+ console.error(`Failed to create/update model ${modelInfo.id}:`, error);
140
+ }
141
+ }
142
+ return NextResponse.json({
143
+ success: true,
144
+ models: results,
145
+ count: results.length,
146
+ });
147
+ }
148
+ catch (error) {
149
+ console.error('Models API error:', error);
150
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
151
+ }
152
+ }
@@ -0,0 +1,118 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getDatabase } from '@/db/database';
3
+ import { getAllProviders, getProviderById, createProvider, updateProvider, deleteProvider } from '@/db/queries';
4
+ import { encryptApiKey, decryptApiKey } from '@/server/crypto';
5
+ // Ensure Node.js runtime (required for SQLite)
6
+ export const runtime = 'nodejs';
7
+ export async function GET(request) {
8
+ try {
9
+ getDatabase();
10
+ const { searchParams } = new URL(request.url);
11
+ const id = searchParams.get('id');
12
+ const includeKey = searchParams.get('includeKey') === 'true';
13
+ // If ID is provided, return single provider (for editing)
14
+ if (id) {
15
+ const provider = getProviderById(parseInt(id));
16
+ if (!provider) {
17
+ return NextResponse.json({ error: 'Provider not found' }, { status: 404 });
18
+ }
19
+ // Return with decrypted API key if requested (for editing)
20
+ return NextResponse.json({
21
+ ...provider,
22
+ api_key: includeKey ? decryptApiKey(provider.api_key) : '***',
23
+ });
24
+ }
25
+ // Return all providers
26
+ const providers = getAllProviders();
27
+ // Don't return API keys in list view
28
+ const sanitized = providers.map(p => ({
29
+ ...p,
30
+ api_key: '***',
31
+ }));
32
+ return NextResponse.json(sanitized);
33
+ }
34
+ catch (error) {
35
+ console.error('Providers API error:', error);
36
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
37
+ }
38
+ }
39
+ export async function POST(request) {
40
+ try {
41
+ getDatabase();
42
+ const body = await request.json();
43
+ const { name, protocol, base_url, api_key } = body;
44
+ if (!name || !protocol || !base_url || !api_key) {
45
+ return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
46
+ }
47
+ const encryptedKey = encryptApiKey(api_key.trim());
48
+ const provider = createProvider({
49
+ name,
50
+ protocol,
51
+ base_url,
52
+ api_key: encryptedKey,
53
+ });
54
+ return NextResponse.json({
55
+ ...provider,
56
+ api_key: '***',
57
+ });
58
+ }
59
+ catch (error) {
60
+ console.error('Providers API error:', error);
61
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
62
+ }
63
+ }
64
+ export async function PUT(request) {
65
+ try {
66
+ getDatabase();
67
+ const body = await request.json();
68
+ const { id, name, protocol, base_url, api_key } = body;
69
+ if (!id) {
70
+ return NextResponse.json({ error: 'Provider ID is required' }, { status: 400 });
71
+ }
72
+ const existingProvider = getProviderById(id);
73
+ if (!existingProvider) {
74
+ return NextResponse.json({ error: 'Provider not found' }, { status: 404 });
75
+ }
76
+ const updateData = {};
77
+ if (name !== undefined)
78
+ updateData.name = name;
79
+ if (protocol !== undefined)
80
+ updateData.protocol = protocol;
81
+ if (base_url !== undefined)
82
+ updateData.base_url = base_url;
83
+ if (api_key !== undefined && api_key !== null && api_key.trim() !== '') {
84
+ updateData.api_key = encryptApiKey(api_key.trim());
85
+ }
86
+ const provider = updateProvider(id, updateData);
87
+ if (!provider) {
88
+ return NextResponse.json({ error: 'Provider not found' }, { status: 404 });
89
+ }
90
+ return NextResponse.json({
91
+ ...provider,
92
+ api_key: '***',
93
+ });
94
+ }
95
+ catch (error) {
96
+ console.error('Providers API error:', error);
97
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
98
+ }
99
+ }
100
+ export async function DELETE(request) {
101
+ try {
102
+ getDatabase();
103
+ const { searchParams } = new URL(request.url);
104
+ const id = searchParams.get('id');
105
+ if (!id) {
106
+ return NextResponse.json({ error: 'Provider ID is required' }, { status: 400 });
107
+ }
108
+ const success = deleteProvider(parseInt(id));
109
+ if (!success) {
110
+ return NextResponse.json({ error: 'Provider not found' }, { status: 404 });
111
+ }
112
+ return NextResponse.json({ success: true });
113
+ }
114
+ catch (error) {
115
+ console.error('Providers API error:', error);
116
+ return NextResponse.json({ error: error.message || 'Internal server error', stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }, { status: 500 });
117
+ }
118
+ }