Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 29 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,52 @@ jobs:

- name: Smoke-test the container
run: |
# The memory limit is the budget check: a container that needs more
# than 512 MB is killed by the kernel, and the health check below
# then fails. No parsing of `docker stats` needed.
docker run -d --name proxy \
--memory 512m \
--memory-swap 512m \
-e API_KEY=sk-dummy \
-p 20128:20128 \
free-opencode-api:ci

# Wait for the proxy to report a healthy opencode server.
UP=0
for i in $(seq 1 30); do
if curl -fsS http://127.0.0.1:20128/health > /dev/null; then
echo "proxy is up"
UP=1
break
fi
if ! docker ps --filter name=proxy --filter status=running -q | grep -q .; then
echo "::error::The container stopped. It probably ran out of memory."
docker logs proxy || true
exit 1
fi
sleep 2
done
if [ "$UP" -ne 1 ]; then
echo "::error::The proxy did not become healthy."
docker logs proxy || true
exit 1
fi

curl -fsS http://127.0.0.1:20128/health
curl -fsS -H 'authorization: Bearer sk-dummy' http://127.0.0.1:20128/v1/models \
| head -c 200
echo

docker logs proxy
echo "the container uses this much memory:"
docker stats --no-stream --format '{{.MemUsage}}' proxy

# The proxy writes no log files and keeps stdout quiet.
LOG_BYTES=$(docker logs proxy 2>&1 | wc -c)
echo "the container wrote $LOG_BYTES bytes to stdout"
if [ "$LOG_BYTES" -ne 0 ]; then
echo "::error::The proxy is supposed to stay silent at the default log level."
docker logs proxy
exit 1
fi

docker rm -f proxy
42 changes: 42 additions & 0 deletions scratch/probe-stream-usage.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Does the SDK's stream accumulator work with and without usage?
import { startMockProvider, startOpencodeWithMock } from "../test/helpers.mjs"
import { buildServer } from "../src/server.js"
import { ModelRegistry } from "../src/model-registry.js"
import { EventBus } from "../src/event-bus.js"

const mock = await startMockProvider({ reply: "Hello from mock" })
const oc = await startOpencodeWithMock(mock)
const client = oc.client()
const registry = new ModelRegistry()
registry.update(await client.providers())
const app = buildServer({ client, registry, eventBus: new EventBus(client) })
await app.listen({ host: "127.0.0.1", port: 0 })
const base = `http://127.0.0.1:${app.server.address().port}/v1`

const { default: OpenAI } = await import("openai")
const openai = new OpenAI({ baseURL: base, apiKey: "sk-dummy" })

for (const includeUsage of [true, false]) {
const stream = await openai.chat.completions.create({
model: "mock/mock-model",
messages: [{ role: "user", content: "Hello" }],
stream: true,
stream_options: { include_usage: includeUsage },
})

let text = ""
let usage = null
let chunks = 0
for await (const chunk of stream) {
chunks++
text += chunk.choices[0]?.delta?.content ?? ""
if (chunk.usage) usage = chunk.usage
}
console.log(`include_usage=${includeUsage}: text=${JSON.stringify(text)} chunks=${chunks} usage=${JSON.stringify(usage)}`)
}

await app.opencode.eventBus.close()
await app.close()
await oc.close()
await mock.close()
process.exit(0)
19 changes: 16 additions & 3 deletions test/disconnect.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,18 @@ test.after(async () => {
await mock?.close()
})

/** The number of sessions opencode is holding. */
/**
* The number of sessions opencode is holding.
*
* The route is project-scoped, so the directory has to be passed — otherwise
* the answer belongs to a different project. An empty body means the server
* is busy rebuilding the instance, which counts as no sessions.
*/
async function sessionCount() {
const list = await (await fetch(`http://127.0.0.1:${oc.port}/session`)).json()
const response = await fetch(`http://127.0.0.1:${oc.port}/session?directory=${encodeURIComponent(oc.dir)}`)
const text = await response.text()
if (!text) return 0
const list = JSON.parse(text)
return Array.isArray(list) ? list.length : 0
}

Expand Down Expand Up @@ -80,7 +89,11 @@ test("a client that disconnects leaves no session behind", async () => {
async function waitForSessions(target, timeout = 30000) {
const deadline = Date.now() + timeout
while (Date.now() < deadline) {
if ((await sessionCount()) <= target) return
try {
if ((await sessionCount()) <= target) return
} catch {
// The server is busy rebuilding the instance; try again.
}
await new Promise((resolve) => setTimeout(resolve, 250))
}
}
Expand Down
Loading