Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
91460f6
feat: Gemini API translation and streaming integration (squashed)
cuipengfei Sep 21, 2025
fa6b612
lint
cuipengfei Sep 21, 2025
edd2875
feat: route in server
cuipengfei Sep 21, 2025
bf5188e
feat: add idle timeout configuration for server
cuipengfei Sep 21, 2025
e6af851
Revert "lint"
cuipengfei Sep 21, 2025
e3e4cf5
fix input_tokens adaptation error
caozhiyuan Aug 28, 2025
acb4cf3
feature claude count token
caozhiyuan Aug 30, 2025
8b7d835
feat(start): add option to generate Claude Code environment variables
caozhiyuan Sep 2, 2025
ce6f058
fix: make usage property optional in AnthropicResponse and remove usa…
caozhiyuan Sep 24, 2025
e0c83ee
feature: token counting for different models
caozhiyuan Sep 10, 2025
8f9f449
Add comprehensive tests for content generation and translation features
cuipengfei Sep 26, 2025
faf03e6
trigger
cuipengfei Sep 26, 2025
8f21b6e
fix: update server import for fallback non-streaming in streaming tests
cuipengfei Sep 26, 2025
a57c238
feature gpt-5-codex responses api
caozhiyuan Sep 26, 2025
d9d9445
Merge pull request #8 from caozhiyuan/feature/count-token
cuipengfei Sep 28, 2025
87899a1
feat: enhance output type for function call and add content conversio…
caozhiyuan Sep 29, 2025
4fc0fa0
refactor: optimize content conversion logic in convertToolResultConte…
caozhiyuan Sep 29, 2025
574d47a
refactor: remove claudeCodeEnv option and update environment variable…
caozhiyuan Sep 30, 2025
2b9733b
refactor: remove unused function call output type and simplify respon…
caozhiyuan Sep 30, 2025
1df7e89
Merge pull request #9 from caozhiyuan/feature/gpt-5-codex
cuipengfei Sep 30, 2025
505f648
feat: add signature and reasoning handling to responses translation a…
caozhiyuan Sep 30, 2025
9477b45
feat: add signature to thinking messages and enhance reasoning struct…
caozhiyuan Sep 30, 2025
44551f9
refactor: remove summaryIndex from ResponsesStreamState and related h…
caozhiyuan Sep 30, 2025
708ae33
feat: enhance streaming response handling with ping mechanism
caozhiyuan Sep 30, 2025
1beeb72
feat: refactor tool translation and streaming handling for improved f…
cuipengfei Oct 1, 2025
47fb3e4
feat: responses translation add cache_read_input_tokens
caozhiyuan Oct 1, 2025
de096e8
refactor: remove unused tool response deduplication function and clea…
cuipengfei Oct 1, 2025
a54b7a6
refactor: update comments in ToolCallAccumulator for clarity and cons…
cuipengfei Oct 1, 2025
fcd2154
refactor: improve comments for clarity in test files
cuipengfei Oct 1, 2025
b287a85
Merge pull request #10 from caozhiyuan/feature/count-token
cuipengfei Oct 1, 2025
4a1f46f
Merge pull request #11 from caozhiyuan/feature/gpt-5-codex
cuipengfei Oct 1, 2025
6975118
Merge branch 'dev' into feature/gemini-api
cuipengfei Oct 1, 2025
4abb0a5
Update tests/generate-content/validation-and-routing.test.ts
cuipengfei Oct 1, 2025
7b3db7a
fix: ensure newline at end of file in server.ts
cuipengfei Oct 1, 2025
48a20c1
fix: handle missing model in token counting endpoint
cuipengfei Oct 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,12 @@ The server exposes several endpoints to interact with the Copilot API. It provid

These endpoints mimic the OpenAI API structure.

| Endpoint | Method | Description |
| --------------------------- | ------ | --------------------------------------------------------- |
| `POST /v1/chat/completions` | `POST` | Creates a model response for the given chat conversation. |
| `GET /v1/models` | `GET` | Lists the currently available models. |
| `POST /v1/embeddings` | `POST` | Creates an embedding vector representing the input text. |
| Endpoint | Method | Description |
| --------------------------- | ------ | ---------------------------------------------------------------- |
| `POST /v1/responses` | `POST` | Most advanced interface for generating model responses. |
| `POST /v1/chat/completions` | `POST` | Creates a model response for the given chat conversation. |
| `GET /v1/models` | `GET` | Lists the currently available models. |
| `POST /v1/embeddings` | `POST` | Creates an embedding vector representing the input text. |

### Anthropic Compatible Endpoints

Expand Down Expand Up @@ -304,7 +305,16 @@ Here is an example `.claude/settings.json` file:
"ANTHROPIC_BASE_URL": "http://localhost:4141",
"ANTHROPIC_AUTH_TOKEN": "dummy",
"ANTHROPIC_MODEL": "gpt-4.1",
"ANTHROPIC_SMALL_FAST_MODEL": "gpt-4.1"
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-4.1",
"ANTHROPIC_SMALL_FAST_MODEL": "gpt-4.1",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-4.1",
"DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"permissions": {
"deny": [
"WebSearch"
]
}
}
```
Expand Down
179 changes: 179 additions & 0 deletions src/lib/debug-logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { existsSync, mkdirSync } from "node:fs"
import { writeFile } from "node:fs/promises"
import { join } from "node:path"

import type { GeminiRequest } from "~/routes/generate-content/types"
import type {
ChatCompletionsPayload,
ChatCompletionResponse,
} from "~/services/copilot/create-chat-completions"

interface DebugLogData {
timestamp: string
requestId: string
originalGeminiPayload: GeminiRequest
translatedOpenAIPayload: ChatCompletionsPayload | null
error?: string
processingTime?: number
}

export class DebugLogger {
private static instance: DebugLogger | undefined
private logDir: string

private constructor() {
this.logDir = process.env.DEBUG_LOG_DIR || join(process.cwd(), "debug-logs")
this.ensureLogDir()
}

static getInstance(): DebugLogger {
if (!DebugLogger.instance) {
DebugLogger.instance = new DebugLogger()
}
return DebugLogger.instance
}

private ensureLogDir(): void {
if (!existsSync(this.logDir)) {
mkdirSync(this.logDir, { recursive: true })
}
}

private generateLogFileName(requestId: string): string {
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, "-")
return join(this.logDir, `debug-gemini-${timestamp}-${requestId}.log`)
}

async logRequest(data: {
requestId: string
geminiPayload: GeminiRequest
openAIPayload?: ChatCompletionsPayload | null
error?: string
processingTime?: number
}): Promise<void> {
const logData: DebugLogData = {
timestamp: new Date().toISOString(),
requestId: data.requestId,
originalGeminiPayload: data.geminiPayload,
translatedOpenAIPayload: data.openAIPayload ?? null,
error: data.error,
processingTime: data.processingTime,
}

const logPath = this.generateLogFileName(data.requestId)

try {
await writeFile(logPath, JSON.stringify(logData, null, 2), "utf8")
console.log(`[DEBUG] Logged request data to: ${logPath}`)
} catch (writeError) {
console.error(`[DEBUG] Failed to write log file ${logPath}:`, writeError)
}
}

// For backward compatibility during development
static async logGeminiRequest(
geminiPayload: GeminiRequest,
openAIPayload?: ChatCompletionsPayload,
error?: string,
): Promise<void> {
const logger = DebugLogger.getInstance()
const requestId = Math.random().toString(36).slice(2, 8)
await logger.logRequest({ requestId, geminiPayload, openAIPayload, error })
}

// Log GitHub Copilot API Response
static async logCopilotResponse(
response: ChatCompletionResponse,
context?: string,
): Promise<void> {
const logger = DebugLogger.getInstance()
const requestId = Math.random().toString(36).slice(2, 8)
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, "-")
const logPath = join(
logger.logDir,
`debug-copilot-response-${timestamp}-${requestId}.log`,
)

const logData = {
timestamp: new Date().toISOString(),
context: context || "GitHub Copilot API Response",
response,
}

try {
await writeFile(logPath, JSON.stringify(logData, null, 2), "utf8")
console.log(`[DEBUG] Logged Copilot response to: ${logPath}`)
} catch (writeError) {
console.error(
`[DEBUG] Failed to write Copilot response log file ${logPath}:`,
writeError,
)
}
}

// Log any object for debugging purposes
static async logDebugData(
data: unknown,
context: string,
filePrefix = "debug-data",
): Promise<void> {
const logger = DebugLogger.getInstance()
const requestId = Math.random().toString(36).slice(2, 8)
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, "-")
const logPath = join(
logger.logDir,
`${filePrefix}-${timestamp}-${requestId}.log`,
)

const logData = {
timestamp: new Date().toISOString(),
context,
data,
}

try {
await writeFile(logPath, JSON.stringify(logData, null, 2), "utf8")
console.log(`[DEBUG] Logged ${context} to: ${logPath}`)
} catch (writeError) {
console.error(
`[DEBUG] Failed to write debug log file ${logPath}:`,
writeError,
)
}
}

// Log original and translated response comparison
static async logResponseComparison(
originalResponse: unknown,
translatedResponse: unknown,
options: { context: string; filePrefix?: string } = {
context: "Response Comparison",
},
): Promise<void> {
const { context, filePrefix = "debug-comparison" } = options
const logger = DebugLogger.getInstance()
const requestId = Math.random().toString(36).slice(2, 8)
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, "-")
const logPath = join(
logger.logDir,
`${filePrefix}-${timestamp}-${requestId}.log`,
)

const logData = {
timestamp: new Date().toISOString(),
context,
originalResponse,
translatedResponse,
}

try {
await writeFile(logPath, JSON.stringify(logData, null, 2), "utf8")
console.log(`[DEBUG] Logged ${context} comparison to: ${logPath}`)
} catch (writeError) {
console.error(
`[DEBUG] Failed to write comparison log file ${logPath}:`,
writeError,
)
}
}
}
Loading
Loading