Conversation
el-pablos <yeteprem.end23juni@gmail.com>
- Add sessionId and userId optional fields to RequestContext - Add generateTraceId() for creating unique trace IDs - Add runWithContext() helper for running code with context - Add getTraceId() function (alias getCurrentTraceId as deprecated) el-pablos <yeteprem.end23juni@gmail.com>
…Delta dan ResponseMessage el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
- Test untuk PATHS constants (APP_DIR, GITHUB_TOKEN_PATH) - Test untuk ensurePaths function (directory creation, file permissions) - Test untuk XDG Base Directory spec compliance - 13 test cases, semua passed el-pablos <yeteprem.end23juni@gmail.com>
- Test createHandlerLogger returns valid logger - Test LogEmitter.log adds entry - Test LogEmitter maintains buffer limit (1000 max) - Test LogEmitter emits events to listeners - Test logger has all methods (info, warn, error, debug, success, box) - Test listener subscribe/unsubscribe functionality - Test error handling for listener errors - Split into 3 files untuk compliance dengan max-lines-per-function el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
el-pablos <yeteprem.end23juni@gmail.com>
- Buat integration tests yang comprehensive untuk stream translation - Test mencakup: 1. Stream state initialization 2. Base chunk creation dengan berbagai overrides 3. Event type helpers (findEvent & filterEvents) 4. THINKING_TEXT constant validation 5. Error translation event 6. Chunk structure validation (thinking, reasoning_opaque, tool calls) 7. State machine transitions 8. Event structure validation (semua event types) 9. Finish reason mapping NOTE: Tests ini dirancang untuk kompatibel dengan implementasi stream-translation.ts yang ada sekarang. Tests yang memerlukan handleReasoningOpaqueSignature dan handleToolCallsDelta tidak disertakan karena fungsi-fungsi tersebut belum diimplementasikan. Semua 21 tests pass dengan 74 expect() calls. el-pablos <yeteprem.end23juni@gmail.com>
… override 2M anti compact, max output 128K, fix port config el-pablos <yeteprem.end23juni@gmail.com>
…-robin pool rotation sekarang work dengan benar via messages api el-pablos <yeteprem.end23juni@gmail.com>
…ang belum ada suffix level el-pablos <yeteprem.end23juni@gmail.com>
…k, model types + dokumentasi round-robin bug fix el-pablos <yeteprem.end23juni@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the proxy to better support “thinking/reasoning” behavior (including Claude-style thinking blocks), adds a native /v1/messages (Anthropic) upstream integration path, and introduces new operational features like file-based logging and configurable truncation/output limits.
Changes:
- Add Messages API client/service and route-level logic to choose between Copilot Chat Completions vs native
/v1/messages, including adaptive/enabled thinking injection and new reasoning fields (reasoning_text,reasoning_opaque) in streaming translation. - Introduce config options for default output tokens and truncation controls (override/disable), plus related truncation behavior updates.
- Add file-based handler logger + request context helpers, and expand test coverage and documentation.
Reviewed changes
Copilot reviewed 34 out of 40 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Excludes tests from TypeScript compilation. |
| tests/create-messages.test.ts | New unit tests for createMessages service behavior and headers. |
| src/webui/routes.ts | Removes model-mappings route; adds force param to version-check API call. |
| src/start.ts | Attempts to allow config/env port override when CLI port is default. |
| src/services/copilot/get-models.ts | Extends model “supports” metadata for thinking budget/adaptive thinking. |
| src/services/copilot/create-messages.ts | New /v1/messages upstream client with header handling + beta filtering. |
| src/services/copilot/chat-completion-types.ts | Adds reasoning fields and thinking_budget to payload/types. |
| src/routes/messages/stream-translation.ts | Adds reasoning/thinking streaming translation helpers and tool-call loop refactor. |
| src/routes/messages/request-payload.ts | Changes default max_tokens resolution to be config-driven. |
| src/routes/messages/non-stream-translation.ts | Adds thinking budget calculation + OpenAI→Anthropic payload “re-translation” helper. |
| src/routes/messages/handler.ts | Major routing refactor for Messages API vs Chat Completions + thinking injection + token accounting for messages streaming. |
| src/routes/messages/tests/thinking-mechanism.test.ts | New unit tests for thinking/opaque signature event translation. |
| src/routes/chat-completions/truncate-messages.ts | Adds config-based truncation disable/override; adjusts safety multiplier for Claude. |
| src/routes/chat-completions/normalize-payload.ts | Auto-injects reasoning_effort from config for GPT-5 models without suffix. |
| src/lib/version-check.ts | Changes remote commit check to ls-remote and adds forced refresh option. |
| src/lib/request-context.ts | Adds trace ID helpers and context runner utilities. |
| src/lib/paths.ts | Expands XDG-style paths and adds ensureDir. |
| src/lib/logger.ts | Adds file-based handler logging with buffering/retention + keeps LogEmitter for compatibility. |
| src/lib/config.ts | Adds default max output tokens + truncation override/disable controls and model effort tweaks. |
| src/lib/account-pool.ts | Adjusts round-robin state persistence and currentIndex behavior on rotation/error. |
| src/lib/account-pool-selection.ts | Fixes round-robin counter logic; adds selection logging. |
| src/lib/tests/paths.test.ts | New tests for PATHS/ensurePaths behavior. |
| src/lib/tests/logger-wrapper.test.ts | New tests for LogEmitter-backed logger wrapper. |
| src/lib/tests/logger-handler.test.ts | New tests for createHandlerLogger. |
| src/lib/tests/logger-emitter.test.ts | New tests for LogEmitter behavior. |
| src/tests/integration/stream-translation.integration.test.ts | New “integration” test scaffold for stream translation/types/helpers. |
| screenshots/.DS_Store | Adds macOS metadata file under screenshots directory. |
| README.md | Large documentation overhaul (usage, architecture, config, endpoints). |
| package.json | Updates package metadata and keywords/description. |
| eslint.config.js | Excludes tests from linting. |
| docs/caching-analysis.md | Adds detailed cache comparison/analysis doc. |
| docs/01-round-robin-bug.md | Adds a round-robin bug report + fix documentation. |
| ANALISIS_PERBANDINGAN_CINA_COPILOT_VS_COPILOT_API.md | Adds a comprehensive comparison report between projects. |
| .gitignore | Adds patterns for env/secrets/tokens/config secrets. |
| .github/workflows/version-bump.yml | Adds a manual version bump workflow. |
| .github/workflows/ci.yml | Splits CI into test + build jobs and narrows branch triggers. |
| .DS_Store | Adds macOS metadata file at repo root. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const LOG_RETENTION_DAYS = 7 | ||
| const LOG_RETENTION_MS = LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000 | ||
| const CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000 | ||
| const LOG_DIR = path.join(PATHS.APP_DIR, "logs") |
There was a problem hiding this comment.
LOG_DIR is derived from PATHS.APP_DIR even though PATHS already exposes LOGS_DIR (under ~/.config/...). This makes log location inconsistent with the central paths configuration and can scatter logs across multiple base dirs. Consider switching to PATHS.LOGS_DIR (or removing LOGS_DIR if logs are intended to live under APP_DIR).
| const LOG_DIR = path.join(PATHS.APP_DIR, "logs") | |
| const LOG_DIR = PATHS.LOGS_DIR |
| // Set up periodic buffer flushing | ||
| setInterval(flushAllBuffers, FLUSH_INTERVAL_MS) | ||
|
|
||
| // Cleanup on process exit | ||
| const cleanup = () => { | ||
| flushAllBuffers() | ||
| for (const stream of logStreams.values()) { | ||
| stream.end() | ||
| } | ||
| logStreams.clear() | ||
| logBuffers.clear() | ||
| } | ||
|
|
||
| process.on("exit", cleanup) | ||
| process.on("SIGINT", () => { | ||
| cleanup() | ||
| process.exit(0) | ||
| }) | ||
| process.on("SIGTERM", () => { | ||
| cleanup() | ||
| process.exit(0) | ||
| }) |
There was a problem hiding this comment.
This module sets up a global setInterval(...) and registers process.on('exit'|'SIGINT'|'SIGTERM') handlers at import time. In test runners (and sometimes in CLIs), an unref'd interval and global signal handlers can keep the process alive or cause hard-to-debug side effects. Consider moving this initialization behind an explicit init function (called from server startup), and/or unref() the interval and avoid installing signal handlers in library modules.
| const config = await loadConfig() | ||
|
|
||
| // Use config.port if different from default (CLI arg default is 4141) | ||
| // This allows config file and PORT env var to override the default | ||
| const actualPort = config.port !== 4141 ? config.port : options.port | ||
|
|
There was a problem hiding this comment.
actualPort uses config.port whenever it differs from 4141, even if the user explicitly passed --port. This makes CLI port overrides ineffective when config sets a non-default port. Consider tracking whether the CLI arg was explicitly provided (or using undefined when not provided) and applying a clear precedence (commonly CLI > env > config > default).
| function resolveMaxTokens(rawMaxTokens: unknown): number { | ||
| if (typeof rawMaxTokens === "number" && Number.isFinite(rawMaxTokens)) { | ||
| return rawMaxTokens | ||
| } | ||
| return 4096 | ||
| // Use configured default for better output capability | ||
| return getDefaultMaxOutputTokens() |
There was a problem hiding this comment.
Defaulting max_tokens to config.defaultMaxOutputTokens (32k) without clamping to the selected model's max_output_tokens can make requests invalid for many models and cause upstream 400/422 errors when clients omit max_tokens. Consider keeping a conservative default, or clamping the configured default to the resolved model limit during normalization.
| // For truncation, we just need to update the messages count | ||
| // while preserving the original Anthropic format | ||
| const truncatedMessageCount = openAIPayload.messages.filter( | ||
| (m) => m.role !== "system", | ||
| ).length | ||
|
|
There was a problem hiding this comment.
translateOpenAIPayloadToAnthropic derives truncatedMessageCount from the OpenAI message array. However, translateAnthropicMessagesToOpenAI() can expand a single Anthropic user message into multiple OpenAI messages (e.g., tool + user), so this count is not equivalent to the original Anthropic messages.length. This can cause the wrong Anthropic messages to be kept/dropped after truncation (and can break tool_use/tool_result pairing).
| return activeAccounts[index] | ||
| const selected = activeAccounts[index] | ||
| poolState.currentIndex++ | ||
| consola.info( |
There was a problem hiding this comment.
consola.info(...) inside selectRoundRobinAccount() will log on every request, which can flood logs and impact performance in high-traffic setups. Consider downgrading this to debug (or guard it behind state.verbose / a config flag).
| consola.info( | |
| consola.debug( |
| describe("ensurePaths", () => { | ||
| const testDir = path.join(os.tmpdir(), "copilot-api-paths-test") | ||
|
|
||
| beforeEach(async () => { | ||
| // Clean up test directory if exists | ||
| try { | ||
| await fs.rm(testDir, { recursive: true, force: true }) | ||
| } catch { | ||
| // Ignore if doesn't exist | ||
| } | ||
| }) | ||
|
|
||
| afterEach(async () => { | ||
| // Clean up after tests | ||
| try { | ||
| await fs.rm(testDir, { recursive: true, force: true }) | ||
| } catch { | ||
| // Ignore if doesn't exist | ||
| } | ||
| }) | ||
|
|
||
| it("ensurePaths creates APP_DIR directory", async () => { | ||
| // Call ensurePaths (it will create the actual APP_DIR) | ||
| await ensurePaths() | ||
|
|
||
| // Verify directory exists | ||
| const stat = await fs.stat(PATHS.APP_DIR) | ||
| expect(stat.isDirectory()).toBe(true) | ||
| }) |
There was a problem hiding this comment.
These tests call ensurePaths() which creates real directories/files under PATHS.APP_DIR in the user's home directory. The testDir variable is cleaned up but never used, so the tests can leave artifacts on developer machines. Consider mocking PATHS/os.homedir() (or refactoring ensurePaths to accept a base dir) so tests operate entirely in a temp directory.
| * NOTE: Some tests are marked as skipped (.skip) because they depend on | ||
| * functions that are not yet implemented in stream-translation.ts: | ||
| * - handleReasoningOpaqueSignature | ||
| * - handleToolCallsDelta | ||
| * | ||
| * These tests will be enabled once those functions are implemented. |
There was a problem hiding this comment.
The top-level doc comment (and the later NOTE) says helper functions in stream-translation.ts are “not yet implemented” and that translateChunkToAnthropicEvents would throw, but those helpers now exist. This comment is now misleading and may cause skipped coverage to remain unnoticed—please update the note and consider enabling the previously skipped integration scenarios.
| * NOTE: Some tests are marked as skipped (.skip) because they depend on | |
| * functions that are not yet implemented in stream-translation.ts: | |
| * - handleReasoningOpaqueSignature | |
| * - handleToolCallsDelta | |
| * | |
| * These tests will be enabled once those functions are implemented. | |
| * NOTE: The helper functions used by these tests (including | |
| * handleReasoningOpaqueSignature and handleToolCallsDelta) are now implemented | |
| * in stream-translation.ts. Tests in this file are expected to run without | |
| * being skipped due to missing helper implementations; any remaining skips | |
| * should be reviewed and documented with up-to-date reasons. |
| "homepage": "https://github.com/el-pablos/copilot-api#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/el-pablos/copilot-api/issues" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/prassaaa/copilot-api.git" | ||
| "url": "https://github.com/el-pablos/copilot-api.git" | ||
| }, |
There was a problem hiding this comment.
Package metadata points to el-pablos/copilot-api for homepage, bugs.url, and repository.url, while other parts of this PR (e.g., README badges/links) point to prassaaa/copilot-api. This inconsistency can confuse users and break npm links. Consider aligning these fields to the actual canonical repository for this package/fork.
| function getRemoteCommit(branch: string): string { | ||
| const output = execSync(`git ls-remote --heads origin ${branch}`, { | ||
| stdio: "pipe", | ||
| }) | ||
| .toString() | ||
| .trim() | ||
|
|
||
| const [sha] = output.split(/\s+/) | ||
| if (!sha) { | ||
| throw new Error(`Cannot resolve origin/${branch}`) | ||
| } | ||
|
|
||
| return sha | ||
| } | ||
|
|
||
| function isLocalUpToDate(local: string, remote: string): boolean { | ||
| if (local === remote) { | ||
| return true | ||
| } | ||
|
|
||
| try { | ||
| execSync(`git merge-base --is-ancestor ${remote} ${local}`, { | ||
| stdio: "ignore", | ||
| }) | ||
| return true | ||
| } catch { | ||
| return false | ||
| } |
There was a problem hiding this comment.
git ls-remote returns a commit SHA that is not guaranteed to exist in the local object database. git merge-base --is-ancestor ${remote} ${local} can fail (even when local is up-to-date or ahead) unless the remote commit has been fetched. Consider fetching the remote branch (as before) or using a comparison that doesn’t require the remote commit object to be present locally.
revisi dari inject xhigh thinking gpt5.x & claude 4.5 model