copilot-router 1.0.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +32 -0
  5. package/dist/lib/api-config.d.ts +15 -0
  6. package/dist/lib/api-config.js +30 -0
  7. package/dist/lib/database.d.ts +60 -0
  8. package/dist/lib/database.js +228 -0
  9. package/dist/lib/error.d.ts +11 -0
  10. package/dist/lib/error.js +34 -0
  11. package/dist/lib/state.d.ts +9 -0
  12. package/dist/lib/state.js +3 -0
  13. package/dist/lib/token-manager.d.ts +95 -0
  14. package/dist/lib/token-manager.js +241 -0
  15. package/dist/lib/utils.d.ts +8 -0
  16. package/dist/lib/utils.js +10 -0
  17. package/dist/main.d.ts +1 -0
  18. package/dist/main.js +97 -0
  19. package/dist/routes/anthropic/routes.d.ts +2 -0
  20. package/dist/routes/anthropic/routes.js +155 -0
  21. package/dist/routes/anthropic/stream-translation.d.ts +3 -0
  22. package/dist/routes/anthropic/stream-translation.js +136 -0
  23. package/dist/routes/anthropic/translation.d.ts +4 -0
  24. package/dist/routes/anthropic/translation.js +241 -0
  25. package/dist/routes/anthropic/types.d.ts +165 -0
  26. package/dist/routes/anthropic/types.js +2 -0
  27. package/dist/routes/anthropic/utils.d.ts +2 -0
  28. package/dist/routes/anthropic/utils.js +12 -0
  29. package/dist/routes/auth/routes.d.ts +2 -0
  30. package/dist/routes/auth/routes.js +158 -0
  31. package/dist/routes/gemini/routes.d.ts +2 -0
  32. package/dist/routes/gemini/routes.js +163 -0
  33. package/dist/routes/gemini/translation.d.ts +5 -0
  34. package/dist/routes/gemini/translation.js +215 -0
  35. package/dist/routes/gemini/types.d.ts +63 -0
  36. package/dist/routes/gemini/types.js +2 -0
  37. package/dist/routes/openai/routes.d.ts +2 -0
  38. package/dist/routes/openai/routes.js +215 -0
  39. package/dist/routes/utility/routes.d.ts +2 -0
  40. package/dist/routes/utility/routes.js +28 -0
  41. package/dist/services/copilot/create-chat-completions.d.ts +130 -0
  42. package/dist/services/copilot/create-chat-completions.js +32 -0
  43. package/dist/services/copilot/create-embeddings.d.ts +20 -0
  44. package/dist/services/copilot/create-embeddings.js +19 -0
  45. package/dist/services/copilot/get-models.d.ts +51 -0
  46. package/dist/services/copilot/get-models.js +45 -0
  47. package/dist/services/github/get-device-code.d.ts +11 -0
  48. package/dist/services/github/get-device-code.js +21 -0
  49. package/dist/services/github/get-user.d.ts +11 -0
  50. package/dist/services/github/get-user.js +17 -0
  51. package/dist/services/github/poll-access-token.d.ts +13 -0
  52. package/dist/services/github/poll-access-token.js +56 -0
  53. package/package.json +56 -0
  54. package/public/index.html +419 -0
@@ -0,0 +1,158 @@
1
+ import consola from "consola";
2
+ import { tokenManager } from "../../lib/token-manager.js";
3
+ import { getDeviceCode } from "../../services/github/get-device-code.js";
4
+ import { checkAccessToken } from "../../services/github/poll-access-token.js";
5
+ // Track device codes being processed to prevent duplicate token additions
6
+ const processingDeviceCodes = new Set();
7
+ export function registerAuthRoutes(app) {
8
+ // POST /auth/login - Start device code flow
9
+ app.post("/auth/login", async (c) => {
10
+ try {
11
+ const deviceCode = await getDeviceCode();
12
+ consola.info(`Login initiated. Please visit ${deviceCode.verification_uri} and enter code: ${deviceCode.user_code}`);
13
+ return c.json({
14
+ user_code: deviceCode.user_code,
15
+ verification_uri: deviceCode.verification_uri,
16
+ device_code: deviceCode.device_code,
17
+ expires_in: deviceCode.expires_in,
18
+ interval: deviceCode.interval,
19
+ });
20
+ }
21
+ catch (error) {
22
+ consola.error("Failed to start login:", error);
23
+ return c.json({ error: { message: "Failed to start login", type: "auth_error" } }, 500);
24
+ }
25
+ });
26
+ // POST /auth/complete - Check device code flow status (single check, client polls)
27
+ app.post("/auth/complete", async (c) => {
28
+ try {
29
+ const body = await c.req.json();
30
+ const { device_code, account_type = "individual" } = body;
31
+ if (!device_code) {
32
+ return c.json({ error: { message: "device_code is required", type: "validation_error" } }, 400);
33
+ }
34
+ // Check access token once
35
+ const result = await checkAccessToken(device_code);
36
+ if (result.error === "authorization_pending") {
37
+ // User hasn't authorized yet, client should continue polling
38
+ return c.json({ status: "pending", message: "Waiting for user authorization" });
39
+ }
40
+ if (result.error === "slow_down") {
41
+ // Rate limited, client should slow down
42
+ return c.json({ status: "slow_down", message: "Please slow down polling" });
43
+ }
44
+ if (result.error === "expired_token") {
45
+ return c.json({ error: { message: "Device code expired. Please try again.", type: "expired" } }, 400);
46
+ }
47
+ if (result.error === "access_denied") {
48
+ return c.json({ error: { message: "Access denied by user.", type: "denied" } }, 400);
49
+ }
50
+ if (result.error) {
51
+ return c.json({ error: { message: result.error_description || result.error, type: "auth_error" } }, 400);
52
+ }
53
+ if (!result.access_token) {
54
+ return c.json({ status: "pending", message: "Waiting for user authorization" });
55
+ }
56
+ // Check if this device code is already being processed
57
+ if (processingDeviceCodes.has(device_code)) {
58
+ return c.json({ status: "processing", message: "Token is being added, please wait" });
59
+ }
60
+ // Mark as processing
61
+ processingDeviceCodes.add(device_code);
62
+ try {
63
+ // Success! Add token to manager
64
+ const entry = await tokenManager.addToken(result.access_token, account_type);
65
+ return c.json({
66
+ status: "success",
67
+ id: entry.id,
68
+ username: entry.username,
69
+ account_type: entry.accountType,
70
+ message: `Successfully logged in as ${entry.username}`,
71
+ });
72
+ }
73
+ finally {
74
+ // Remove from processing set
75
+ processingDeviceCodes.delete(device_code);
76
+ }
77
+ }
78
+ catch (error) {
79
+ consola.error("Failed to complete login:", error);
80
+ return c.json({ error: { message: "Failed to complete login", type: "auth_error" } }, 500);
81
+ }
82
+ });
83
+ // GET /auth/tokens - List all tokens
84
+ app.get("/auth/tokens", (c) => {
85
+ try {
86
+ const stats = tokenManager.getStatistics();
87
+ const counts = tokenManager.getTokenCount();
88
+ return c.json({
89
+ total: counts.total,
90
+ active: counts.active,
91
+ tokens: stats.map((s) => ({
92
+ id: s.id,
93
+ username: s.username,
94
+ account_type: s.accountType,
95
+ is_active: s.isActive,
96
+ has_copilot_token: s.hasValidCopilotToken,
97
+ copilot_token_expires_at: s.copilotTokenExpiresAt?.toISOString() ?? null,
98
+ request_count: s.requestCount,
99
+ error_count: s.errorCount,
100
+ last_used: s.lastUsed?.toISOString() ?? null,
101
+ })),
102
+ });
103
+ }
104
+ catch (error) {
105
+ consola.error("Failed to list tokens:", error);
106
+ return c.json({ error: { message: "Failed to list tokens", type: "error" } }, 500);
107
+ }
108
+ });
109
+ // DELETE /auth/tokens/all - Delete all tokens (for cleanup) - must be before :id route
110
+ app.delete("/auth/tokens/all", async (c) => {
111
+ try {
112
+ await tokenManager.removeAllTokens();
113
+ return c.json({ message: "All tokens deleted" });
114
+ }
115
+ catch (error) {
116
+ consola.error("Failed to delete all tokens:", error);
117
+ return c.json({ error: { message: "Failed to delete all tokens", type: "error" } }, 500);
118
+ }
119
+ });
120
+ // DELETE /auth/tokens/:id - Delete a token
121
+ app.delete("/auth/tokens/:id", async (c) => {
122
+ try {
123
+ const id = parseInt(c.req.param("id"), 10);
124
+ if (isNaN(id)) {
125
+ return c.json({ error: { message: "Invalid token ID", type: "validation_error" } }, 400);
126
+ }
127
+ const removed = await tokenManager.removeToken(id);
128
+ if (!removed) {
129
+ return c.json({ error: { message: "Token not found", type: "not_found" } }, 404);
130
+ }
131
+ return c.json({ message: "Token deleted successfully" });
132
+ }
133
+ catch (error) {
134
+ consola.error("Failed to delete token:", error);
135
+ return c.json({ error: { message: "Failed to delete token", type: "error" } }, 500);
136
+ }
137
+ });
138
+ // POST /auth/tokens - Add token directly
139
+ app.post("/auth/tokens", async (c) => {
140
+ try {
141
+ const body = await c.req.json();
142
+ const { github_token, account_type = "individual" } = body;
143
+ if (!github_token) {
144
+ return c.json({ error: { message: "github_token is required", type: "validation_error" } }, 400);
145
+ }
146
+ const entry = await tokenManager.addToken(github_token, account_type);
147
+ return c.json({
148
+ id: entry.id,
149
+ username: entry.username,
150
+ message: `Token added for ${entry.username}`,
151
+ });
152
+ }
153
+ catch (error) {
154
+ consola.error("Failed to add token:", error);
155
+ return c.json({ error: { message: "Failed to add token", type: "error" } }, 500);
156
+ }
157
+ });
158
+ }
@@ -0,0 +1,2 @@
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
+ export declare function registerGeminiRoutes(app: OpenAPIHono): void;
@@ -0,0 +1,163 @@
1
+ import { createRoute, z } from "@hono/zod-openapi";
2
+ import { streamSSE } from "hono/streaming";
3
+ import consola from "consola";
4
+ import { forwardError } from "../../lib/error.js";
5
+ import { createChatCompletions, } from "../../services/copilot/create-chat-completions.js";
6
+ import { translateGeminiToOpenAI, translateOpenAIToGemini, translateChunkToGemini, } from "./translation.js";
7
+ const GeminiErrorResponseSchema = z.object({
8
+ error: z.object({
9
+ code: z.number(),
10
+ message: z.string(),
11
+ status: z.string(),
12
+ }),
13
+ });
14
+ // Generate content route (non-streaming)
15
+ const generateContentRoute = createRoute({
16
+ method: "post",
17
+ path: "/models/:modelWithMethod",
18
+ tags: ["Gemini API"],
19
+ summary: "Generate content with Gemini-compatible API",
20
+ description: "Generate content using the Gemini-compatible API interface, powered by GitHub Copilot.",
21
+ request: {
22
+ params: z.object({
23
+ modelWithMethod: z.string(),
24
+ }),
25
+ body: {
26
+ content: {
27
+ "application/json": {
28
+ schema: z.object({}).passthrough(),
29
+ },
30
+ },
31
+ },
32
+ },
33
+ responses: {
34
+ 200: {
35
+ content: {
36
+ "application/json": {
37
+ schema: z.object({}).passthrough(),
38
+ },
39
+ "text/event-stream": {
40
+ schema: z.string(),
41
+ },
42
+ },
43
+ description: "Successfully generated content",
44
+ },
45
+ 400: {
46
+ content: { "application/json": { schema: GeminiErrorResponseSchema } },
47
+ description: "Bad request",
48
+ },
49
+ 500: {
50
+ content: { "application/json": { schema: GeminiErrorResponseSchema } },
51
+ description: "Internal server error",
52
+ },
53
+ },
54
+ });
55
+ // Count tokens route
56
+ const countTokensRoute = createRoute({
57
+ method: "post",
58
+ path: "/models/:modelWithMethod",
59
+ tags: ["Gemini API"],
60
+ summary: "Count tokens for Gemini-compatible request",
61
+ description: "Count the tokens for a request using the Gemini-compatible API interface.",
62
+ request: {
63
+ params: z.object({
64
+ modelWithMethod: z.string(),
65
+ }),
66
+ body: {
67
+ content: {
68
+ "application/json": {
69
+ schema: z.object({}).passthrough(),
70
+ },
71
+ },
72
+ },
73
+ },
74
+ responses: {
75
+ 200: {
76
+ content: {
77
+ "application/json": {
78
+ schema: z.object({
79
+ totalTokens: z.number(),
80
+ }),
81
+ },
82
+ },
83
+ description: "Successfully counted tokens",
84
+ },
85
+ 500: {
86
+ content: { "application/json": { schema: GeminiErrorResponseSchema } },
87
+ description: "Internal server error",
88
+ },
89
+ },
90
+ });
91
+ const isNonStreaming = (response) => Object.hasOwn(response, "choices");
92
+ function parseModelFromPath(modelWithMethod) {
93
+ // Format: gemini-2.5-pro:generateContent or gemini-2.5-pro:streamGenerateContent
94
+ const colonIndex = modelWithMethod.lastIndexOf(":");
95
+ if (colonIndex === -1) {
96
+ return { model: modelWithMethod, method: "generateContent" };
97
+ }
98
+ return {
99
+ model: modelWithMethod.substring(0, colonIndex),
100
+ method: modelWithMethod.substring(colonIndex + 1),
101
+ };
102
+ }
103
+ export function registerGeminiRoutes(app) {
104
+ // Handle all Gemini routes with pattern matching
105
+ app.post("/models/:modelWithMethod", async (c) => {
106
+ try {
107
+ const modelWithMethod = c.req.param("modelWithMethod");
108
+ const { model, method } = parseModelFromPath(modelWithMethod);
109
+ consola.debug(`Gemini request: model=${model}, method=${method}`);
110
+ const geminiPayload = await c.req.json();
111
+ if (method === "countTokens") {
112
+ // Simple token estimation
113
+ let totalChars = 0;
114
+ if (geminiPayload.contents) {
115
+ for (const content of geminiPayload.contents) {
116
+ for (const part of content.parts || []) {
117
+ if (part.text) {
118
+ totalChars += part.text.length;
119
+ }
120
+ }
121
+ }
122
+ }
123
+ if (geminiPayload.systemInstruction) {
124
+ for (const part of geminiPayload.systemInstruction.parts || []) {
125
+ if (part.text) {
126
+ totalChars += part.text.length;
127
+ }
128
+ }
129
+ }
130
+ const estimatedTokens = Math.ceil(totalChars / 4);
131
+ return c.json({ totalTokens: estimatedTokens });
132
+ }
133
+ const isStreaming = method === "streamGenerateContent";
134
+ const openAIPayload = translateGeminiToOpenAI(geminiPayload, model);
135
+ openAIPayload.stream = isStreaming;
136
+ consola.debug("Translated OpenAI payload:", JSON.stringify(openAIPayload));
137
+ const response = await createChatCompletions(openAIPayload);
138
+ if (isNonStreaming(response)) {
139
+ const geminiResponse = translateOpenAIToGemini(response);
140
+ return c.json(geminiResponse);
141
+ }
142
+ // Streaming response
143
+ return streamSSE(c, async (stream) => {
144
+ for await (const rawEvent of response) {
145
+ if (rawEvent.data === "[DONE]")
146
+ break;
147
+ if (!rawEvent.data)
148
+ continue;
149
+ const chunk = JSON.parse(rawEvent.data);
150
+ const geminiChunk = translateChunkToGemini(chunk);
151
+ if (geminiChunk.candidates && geminiChunk.candidates.length > 0) {
152
+ await stream.writeSSE({
153
+ data: JSON.stringify(geminiChunk),
154
+ });
155
+ }
156
+ }
157
+ });
158
+ }
159
+ catch (error) {
160
+ return await forwardError(c, error);
161
+ }
162
+ });
163
+ }
@@ -0,0 +1,5 @@
1
+ import { type ChatCompletionResponse, type ChatCompletionsPayload, type ChatCompletionChunk } from "../../services/copilot/create-chat-completions.js";
2
+ import { type GeminiGenerateContentRequest, type GeminiGenerateContentResponse } from "./types.js";
3
+ export declare function translateGeminiToOpenAI(request: GeminiGenerateContentRequest, model: string): ChatCompletionsPayload;
4
+ export declare function translateOpenAIToGemini(response: ChatCompletionResponse): GeminiGenerateContentResponse;
5
+ export declare function translateChunkToGemini(chunk: ChatCompletionChunk): GeminiGenerateContentResponse;
@@ -0,0 +1,215 @@
1
+ // Gemini -> OpenAI translation
2
+ export function translateGeminiToOpenAI(request, model) {
3
+ const messages = [];
4
+ // Handle system instruction
5
+ if (request.systemInstruction) {
6
+ const systemText = request.systemInstruction.parts
7
+ ?.map((p) => p.text)
8
+ .filter(Boolean)
9
+ .join("\n");
10
+ if (systemText) {
11
+ messages.push({ role: "system", content: systemText });
12
+ }
13
+ }
14
+ // Handle contents
15
+ if (request.contents) {
16
+ for (const content of request.contents) {
17
+ const role = content.role === "model" ? "assistant" : "user";
18
+ const parts = content.parts || [];
19
+ // Check for function calls/responses
20
+ const functionCalls = parts.filter((p) => p.functionCall);
21
+ const functionResponses = parts.filter((p) => p.functionResponse);
22
+ const textParts = parts.filter((p) => p.text);
23
+ if (functionCalls.length > 0) {
24
+ // Assistant with tool calls
25
+ const textContent = textParts.map((p) => p.text).join("\n") || null;
26
+ messages.push({
27
+ role: "assistant",
28
+ content: textContent,
29
+ tool_calls: functionCalls.map((p, idx) => ({
30
+ id: p.functionCall.id || `call_${idx}`,
31
+ type: "function",
32
+ function: {
33
+ name: p.functionCall.name,
34
+ arguments: JSON.stringify(p.functionCall.args || {}),
35
+ },
36
+ })),
37
+ });
38
+ }
39
+ else if (functionResponses.length > 0) {
40
+ // Tool results
41
+ for (const p of functionResponses) {
42
+ messages.push({
43
+ role: "tool",
44
+ tool_call_id: p.functionResponse.id,
45
+ content: JSON.stringify(p.functionResponse.response || ""),
46
+ });
47
+ }
48
+ }
49
+ else {
50
+ // Regular text message
51
+ const textContent = textParts.map((p) => p.text).join("\n") || "";
52
+ // Handle inline data (images)
53
+ const imageParts = parts.filter((p) => p.inlineData);
54
+ if (imageParts.length > 0) {
55
+ messages.push({
56
+ role: role,
57
+ content: [
58
+ ...textParts.map((p) => ({ type: "text", text: p.text })),
59
+ ...imageParts.map((p) => ({
60
+ type: "image_url",
61
+ image_url: {
62
+ url: `data:${p.inlineData.mimeType};base64,${p.inlineData.data}`,
63
+ },
64
+ })),
65
+ ],
66
+ });
67
+ }
68
+ else {
69
+ messages.push({
70
+ role: role,
71
+ content: textContent,
72
+ });
73
+ }
74
+ }
75
+ }
76
+ }
77
+ // Translate tools
78
+ let tools;
79
+ if (request.tools) {
80
+ tools = [];
81
+ for (const tool of request.tools) {
82
+ if (tool.functionDeclarations) {
83
+ for (const func of tool.functionDeclarations) {
84
+ tools.push({
85
+ type: "function",
86
+ function: {
87
+ name: func.name,
88
+ description: func.description,
89
+ parameters: func.parameters || {},
90
+ },
91
+ });
92
+ }
93
+ }
94
+ }
95
+ }
96
+ // Translate tool config
97
+ let tool_choice;
98
+ if (request.toolConfig?.functionCallingConfig?.mode) {
99
+ const mode = request.toolConfig.functionCallingConfig.mode;
100
+ if (mode === "AUTO")
101
+ tool_choice = "auto";
102
+ else if (mode === "ANY")
103
+ tool_choice = "required";
104
+ else if (mode === "NONE")
105
+ tool_choice = "none";
106
+ }
107
+ return {
108
+ model,
109
+ messages,
110
+ temperature: request.generationConfig?.temperature,
111
+ top_p: request.generationConfig?.topP,
112
+ max_tokens: request.generationConfig?.maxOutputTokens,
113
+ stop: request.generationConfig?.stopSequences,
114
+ tools: tools && tools.length > 0 ? tools : undefined,
115
+ tool_choice,
116
+ };
117
+ }
118
+ // OpenAI -> Gemini translation
119
+ export function translateOpenAIToGemini(response) {
120
+ const candidates = [];
121
+ for (const choice of response.choices) {
122
+ const parts = [];
123
+ // Handle text content
124
+ if (choice.message.content) {
125
+ parts.push({ text: choice.message.content });
126
+ }
127
+ // Handle tool calls
128
+ if (choice.message.tool_calls) {
129
+ for (const toolCall of choice.message.tool_calls) {
130
+ parts.push({
131
+ functionCall: {
132
+ id: toolCall.id,
133
+ name: toolCall.function.name,
134
+ args: JSON.parse(toolCall.function.arguments),
135
+ },
136
+ });
137
+ }
138
+ }
139
+ const content = {
140
+ parts,
141
+ role: "model",
142
+ };
143
+ const finishReasonMap = {
144
+ stop: "STOP",
145
+ length: "MAX_TOKENS",
146
+ tool_calls: "STOP",
147
+ content_filter: "SAFETY",
148
+ };
149
+ candidates.push({
150
+ content,
151
+ finishReason: finishReasonMap[choice.finish_reason] || "OTHER",
152
+ index: choice.index,
153
+ });
154
+ }
155
+ return {
156
+ candidates,
157
+ usageMetadata: response.usage
158
+ ? {
159
+ promptTokenCount: response.usage.prompt_tokens,
160
+ candidatesTokenCount: response.usage.completion_tokens,
161
+ totalTokenCount: response.usage.total_tokens,
162
+ }
163
+ : undefined,
164
+ };
165
+ }
166
+ // Stream chunk translation
167
+ export function translateChunkToGemini(chunk) {
168
+ const candidates = [];
169
+ for (const choice of chunk.choices) {
170
+ const parts = [];
171
+ if (choice.delta.content) {
172
+ parts.push({ text: choice.delta.content });
173
+ }
174
+ if (choice.delta.tool_calls) {
175
+ for (const toolCall of choice.delta.tool_calls) {
176
+ if (toolCall.function?.name) {
177
+ parts.push({
178
+ functionCall: {
179
+ id: toolCall.id,
180
+ name: toolCall.function.name,
181
+ args: toolCall.function.arguments
182
+ ? JSON.parse(toolCall.function.arguments)
183
+ : {},
184
+ },
185
+ });
186
+ }
187
+ }
188
+ }
189
+ if (parts.length > 0) {
190
+ const finishReasonMap = {
191
+ stop: "STOP",
192
+ length: "MAX_TOKENS",
193
+ tool_calls: "STOP",
194
+ content_filter: "SAFETY",
195
+ };
196
+ candidates.push({
197
+ content: { parts, role: "model" },
198
+ finishReason: choice.finish_reason
199
+ ? finishReasonMap[choice.finish_reason] || "OTHER"
200
+ : undefined,
201
+ index: choice.index,
202
+ });
203
+ }
204
+ }
205
+ return {
206
+ candidates,
207
+ usageMetadata: chunk.usage
208
+ ? {
209
+ promptTokenCount: chunk.usage.prompt_tokens,
210
+ candidatesTokenCount: chunk.usage.completion_tokens,
211
+ totalTokenCount: chunk.usage.total_tokens,
212
+ }
213
+ : undefined,
214
+ };
215
+ }
@@ -0,0 +1,63 @@
1
+ export interface GeminiContent {
2
+ parts: GeminiPart[];
3
+ role?: "user" | "model";
4
+ }
5
+ export interface GeminiPart {
6
+ text?: string;
7
+ inlineData?: {
8
+ mimeType: string;
9
+ data: string;
10
+ };
11
+ functionCall?: {
12
+ id?: string;
13
+ name: string;
14
+ args?: Record<string, unknown>;
15
+ };
16
+ functionResponse?: {
17
+ id: string;
18
+ response?: unknown;
19
+ };
20
+ }
21
+ export interface GeminiTool {
22
+ functionDeclarations?: GeminiFunctionDeclaration[];
23
+ }
24
+ export interface GeminiFunctionDeclaration {
25
+ name: string;
26
+ description?: string;
27
+ parameters?: Record<string, unknown>;
28
+ }
29
+ export interface GeminiToolConfig {
30
+ functionCallingConfig?: {
31
+ mode?: "AUTO" | "ANY" | "NONE";
32
+ allowedFunctionNames?: string[];
33
+ };
34
+ }
35
+ export interface GeminiGenerationConfig {
36
+ temperature?: number;
37
+ topP?: number;
38
+ topK?: number;
39
+ maxOutputTokens?: number;
40
+ stopSequences?: string[];
41
+ }
42
+ export interface GeminiGenerateContentRequest {
43
+ contents?: GeminiContent[];
44
+ tools?: GeminiTool[];
45
+ toolConfig?: GeminiToolConfig;
46
+ systemInstruction?: GeminiContent;
47
+ generationConfig?: GeminiGenerationConfig;
48
+ }
49
+ export interface GeminiCandidate {
50
+ content: GeminiContent;
51
+ finishReason?: "STOP" | "MAX_TOKENS" | "SAFETY" | "RECITATION" | "OTHER";
52
+ index?: number;
53
+ }
54
+ export interface GeminiUsageMetadata {
55
+ promptTokenCount?: number;
56
+ candidatesTokenCount?: number;
57
+ totalTokenCount?: number;
58
+ }
59
+ export interface GeminiGenerateContentResponse {
60
+ candidates?: GeminiCandidate[];
61
+ usageMetadata?: GeminiUsageMetadata;
62
+ modelVersion?: string;
63
+ }
@@ -0,0 +1,2 @@
1
+ // Gemini API Types
2
+ export {};
@@ -0,0 +1,2 @@
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
+ export declare function registerOpenAIRoutes(app: OpenAPIHono): void;