Skip to content

Commit 2800ed3

Browse files
committed
feat: enhance response event handling with event types and improved parsing and allign with vscode-copilot-chat extractThinkingData, otherwise it will cause miss cache occasionally
2 parents 47fb3e4 + 025d284 commit 2800ed3

28 files changed

+1183
-761
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
run: bun install
2121

2222
- name: Run linter
23-
run: bun run lint
23+
run: bun run lint:all
2424

2525
- name: Run type check
2626
run: bun run typecheck
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: Docker Build and Push
2+
3+
# This workflow uses actions that are not certified by GitHub.
4+
# They are provided by a third-party and are governed by
5+
# separate terms of service, privacy policy, and support
6+
# documentation.
7+
8+
on:
9+
push:
10+
# branches: [ "main" ]
11+
# Publish semver tags as releases.
12+
tags: [ 'v*.*.*' ]
13+
paths-ignore:
14+
- 'docs/**'
15+
16+
env:
17+
# Use docker.io for Docker Hub if empty
18+
REGISTRY: ghcr.io
19+
# github.repository as <account>/<repo>
20+
#IMAGE_NAME: ${{ github.repository }}
21+
22+
23+
jobs:
24+
build:
25+
runs-on: ubuntu-latest
26+
permissions:
27+
contents: read
28+
packages: write
29+
# This is used to complete the identity challenge
30+
# with sigstore/fulcio when running outside of PRs.
31+
id-token: write
32+
33+
steps:
34+
- name: Checkout repository
35+
uses: actions/checkout@v3
36+
37+
- name: Set version
38+
id: version
39+
run: |
40+
mkdir -p handlers
41+
echo ${GITHUB_REF#refs/tags/v} > handlers/VERSION
42+
43+
# Install the cosign tool except on PR
44+
# https://github.com/sigstore/cosign-installer
45+
- name: Install cosign
46+
if: github.event_name != 'pull_request'
47+
uses: sigstore/cosign-installer@main
48+
- name: Set up QEMU
49+
uses: docker/setup-qemu-action@v2
50+
with:
51+
platforms: 'arm64,amd64'
52+
53+
# Workaround: https://github.com/docker/build-push-action/issues/461
54+
- name: Setup Docker buildx
55+
uses: docker/setup-buildx-action@v2
56+
57+
# Login against a Docker registry except on PR
58+
# https://github.com/docker/login-action
59+
- name: Log into registry ${{ env.REGISTRY }}
60+
if: github.event_name != 'pull_request'
61+
uses: docker/login-action@v2
62+
with:
63+
registry: ${{ env.REGISTRY }}
64+
username: ${{ github.actor }}
65+
password: ${{ secrets.GITHUB_TOKEN }}
66+
67+
# Extract metadata (tags, labels) for Docker
68+
# https://github.com/docker/metadata-action
69+
- name: Extract Docker metadata
70+
id: meta
71+
uses: docker/metadata-action@v4
72+
with:
73+
github-token: ${{ secrets.GITHUB_TOKEN }}
74+
images: ${{ env.REGISTRY }}/${{ github.repository }}
75+
tags: |
76+
type=semver,pattern=v{{version}}
77+
type=semver,pattern=v{{major}}.{{minor}}
78+
type=semver,pattern=v{{major}}
79+
80+
# Build and push Docker image with Buildx (don't push on PR)
81+
# https://github.com/docker/build-push-action
82+
- name: Build and push Docker image
83+
id: build-and-push
84+
uses: docker/build-push-action@v3
85+
with:
86+
context: .
87+
push: ${{ github.event_name != 'pull_request' }}
88+
tags: ${{ steps.meta.outputs.tags }}
89+
platforms: linux/amd64,linux/arm64
90+
labels: ${{ steps.meta.outputs.labels }}
91+

Dockerfile

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ EXPOSE 4141
2020
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
2121
CMD wget --spider -q http://localhost:4141/ || exit 1
2222

23-
ARG GH_TOKEN
24-
ENV GH_TOKEN=$GH_TOKEN
25-
26-
ENTRYPOINT ["bun", "run", "dist/main.js"]
27-
CMD ["start", "-g", "$GH_TOKEN"]
23+
COPY entrypoint.sh /entrypoint.sh
24+
RUN chmod +x /entrypoint.sh
25+
ENTRYPOINT ["/entrypoint.sh"]

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,16 @@ Here is an example `.claude/settings.json` file:
305305
"ANTHROPIC_BASE_URL": "http://localhost:4141",
306306
"ANTHROPIC_AUTH_TOKEN": "dummy",
307307
"ANTHROPIC_MODEL": "gpt-4.1",
308-
"ANTHROPIC_SMALL_FAST_MODEL": "gpt-4.1"
308+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-4.1",
309+
"ANTHROPIC_SMALL_FAST_MODEL": "gpt-4.1",
310+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-4.1",
311+
"DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
312+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
313+
},
314+
"permissions": {
315+
"deny": [
316+
"WebSearch"
317+
]
309318
}
310319
}
311320
```

bun.lock

Lines changed: 192 additions & 310 deletions
Large diffs are not rendered by default.

entrypoint.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
if [ "$1" = "--auth" ]; then
3+
# Run auth command
4+
exec bun run dist/main.js auth
5+
else
6+
# Default command
7+
exec bun run dist/main.js start -g "$GH_TOKEN" "$@"
8+
fi
9+

package.json

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "copilot-api",
3-
"version": "0.5.14",
3+
"version": "0.6.1",
44
"description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
55
"keywords": [
66
"proxy",
@@ -25,7 +25,8 @@
2525
"build": "tsdown",
2626
"dev": "bun run --watch ./src/main.ts",
2727
"knip": "knip-bun",
28-
"lint": "eslint . --cache",
28+
"lint": "eslint --cache",
29+
"lint:all": "eslint --cache .",
2930
"prepack": "bun run build",
3031
"prepare": "simple-git-hooks",
3132
"release": "bumpp && bun publish --access public",
@@ -40,24 +41,28 @@
4041
},
4142
"dependencies": {
4243
"citty": "^0.1.6",
43-
"clipboardy": "^4.0.0",
44+
"clipboardy": "^5.0.0",
4445
"consola": "^3.4.2",
4546
"fetch-event-stream": "^0.1.5",
4647
"gpt-tokenizer": "^3.0.1",
47-
"hono": "^4.9.6",
48-
"srvx": "^0.8.7",
49-
"tiny-invariant": "^1.3.3"
48+
"hono": "^4.9.9",
49+
"proxy-from-env": "^1.1.0",
50+
"srvx": "^0.8.9",
51+
"tiny-invariant": "^1.3.3",
52+
"undici": "^7.16.0",
53+
"zod": "^4.1.11"
5054
},
5155
"devDependencies": {
5256
"@echristian/eslint-config": "^0.0.54",
53-
"@types/bun": "^1.2.21",
57+
"@types/bun": "^1.2.23",
58+
"@types/proxy-from-env": "^1.0.4",
5459
"bumpp": "^10.2.3",
55-
"eslint": "^9.35.0",
56-
"knip": "^5.63.1",
57-
"lint-staged": "^16.1.6",
60+
"eslint": "^9.37.0",
61+
"knip": "^5.64.1",
62+
"lint-staged": "^16.2.3",
5863
"prettier-plugin-packagejson": "^2.5.19",
5964
"simple-git-hooks": "^2.13.1",
60-
"tsdown": "^0.14.2",
61-
"typescript": "^5.9.2"
65+
"tsdown": "^0.15.6",
66+
"typescript": "^5.9.3"
6267
}
6368
}

src/lib/proxy.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import consola from "consola"
2+
import { getProxyForUrl } from "proxy-from-env"
3+
import { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from "undici"
4+
5+
export function initProxyFromEnv(): void {
6+
if (typeof Bun !== "undefined") return
7+
8+
try {
9+
const direct = new Agent()
10+
const proxies = new Map<string, ProxyAgent>()
11+
12+
// We only need a minimal dispatcher that implements `dispatch` at runtime.
13+
// Typing the object as `Dispatcher` forces TypeScript to require many
14+
// additional methods. Instead, keep a plain object and cast when passing
15+
// to `setGlobalDispatcher`.
16+
const dispatcher = {
17+
dispatch(
18+
options: Dispatcher.DispatchOptions,
19+
handler: Dispatcher.DispatchHandler,
20+
) {
21+
try {
22+
const origin =
23+
typeof options.origin === "string" ?
24+
new URL(options.origin)
25+
: (options.origin as URL)
26+
const get = getProxyForUrl as unknown as (
27+
u: string,
28+
) => string | undefined
29+
const raw = get(origin.toString())
30+
const proxyUrl = raw && raw.length > 0 ? raw : undefined
31+
if (!proxyUrl) {
32+
consola.debug(`HTTP proxy bypass: ${origin.hostname}`)
33+
return (direct as unknown as Dispatcher).dispatch(options, handler)
34+
}
35+
let agent = proxies.get(proxyUrl)
36+
if (!agent) {
37+
agent = new ProxyAgent(proxyUrl)
38+
proxies.set(proxyUrl, agent)
39+
}
40+
let label = proxyUrl
41+
try {
42+
const u = new URL(proxyUrl)
43+
label = `${u.protocol}//${u.host}`
44+
} catch {
45+
/* noop */
46+
}
47+
consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`)
48+
return (agent as unknown as Dispatcher).dispatch(options, handler)
49+
} catch {
50+
return (direct as unknown as Dispatcher).dispatch(options, handler)
51+
}
52+
},
53+
close() {
54+
return direct.close()
55+
},
56+
destroy() {
57+
return direct.destroy()
58+
},
59+
}
60+
61+
setGlobalDispatcher(dispatcher as unknown as Dispatcher)
62+
consola.debug("HTTP proxy configured from environment (per-URL)")
63+
} catch (err) {
64+
consola.debug("Proxy setup skipped:", err)
65+
}
66+
}

0 commit comments

Comments
 (0)