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
45 changes: 45 additions & 0 deletions .github/workflows/build-agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- "service/**"
- "agent/**"
- "agent-ui/**"
- ".github/workflows/build-agent.yaml"
- "*.gradle.kts"
- "gradle/**"

Expand All @@ -29,6 +30,28 @@ jobs:
distribution: temurin
java-version: 21

- name: Restore stable Agent debug signing key
uses: actions/cache@v5
with:
path: ~/.android/debug.keystore
key: clash-meta-agent-debug-keystore-v1

- name: Ensure stable Agent debug signing key
shell: bash
run: |
mkdir -p "$HOME/.android"
if [ ! -f "$HOME/.android/debug.keystore" ]; then
keytool -genkeypair -v \
-keystore "$HOME/.android/debug.keystore" \
-storepass android \
-alias androiddebugkey \
-keypass android \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-dname "CN=Clash Meta AI Debug,O=Clash Meta AI,C=CN"
fi

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5

Expand All @@ -54,9 +77,31 @@ jobs:
sudo update-ca-certificates
cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt

- name: Stress-test incremental streaming patches
run: ./gradlew --no-daemon app:testAgentDebugUnitTest

- name: Build arm64 APK
run: ./gradlew --no-daemon -PagentArm64Only=true app:assembleAgentDebug

- name: Verify stable signing identity
shell: bash
run: |
apk="$(find app/build/outputs/apk/agent/debug -name '*-arm64-v8a-*.apk' -print -quit)"
apksigner="$(find "$ANDROID_HOME/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
keytool -exportcert \
-keystore "$HOME/.android/debug.keystore" \
-storepass android \
-alias androiddebugkey \
-file "$RUNNER_TEMP/agent-debug-cert.der"
key_fingerprint="$(sha256sum "$RUNNER_TEMP/agent-debug-cert.der" | cut -d' ' -f1)"
apk_fingerprint="$("$apksigner" verify --print-certs "$apk" | sed -n 's/.*certificate SHA-256 digest: //p' | head -n 1)"
test -n "$key_fingerprint"
test -n "$apk_fingerprint"
echo "Cached key certificate: ${key_fingerprint,,}"
echo "Built APK certificate: ${apk_fingerprint,,}"
test "${key_fingerprint,,}" = "${apk_fingerprint,,}"
echo "Verified stable APK signing certificate: ${apk_fingerprint}"

- name: Upload arm64 APK
uses: actions/upload-artifact@v7
with:
Expand Down
22 changes: 22 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
3th-party software licenses

* markstream-core smooth streaming algorithm
==========================================================================
Copyright (c) 2022 Simon He

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

* Clash
==========================================================================
GNU GENERAL PUBLIC LICENSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,21 @@ class OpenAICompatibleClient(
put("model", settings.model)
put("messages", messages)
put("stream", true)
put("tools", buildJsonArray {
tools.forEach { tool ->
add(buildJsonObject {
put("type", "function")
put("function", buildJsonObject {
put("name", tool.name)
put("description", tool.description)
put("parameters", tool.parameters)
if (tools.isNotEmpty()) {
put("tools", buildJsonArray {
tools.forEach { tool ->
add(buildJsonObject {
put("type", "function")
put("function", buildJsonObject {
put("name", tool.name)
put("description", tool.description)
put("parameters", tool.parameters)
})
})
})
}
})
put("tool_choice", "auto")
}
})
put("tool_choice", "auto")
}
}

try {
Expand All @@ -97,6 +99,17 @@ class OpenAICompatibleClient(
}
}

/** Performs a minimal real chat completion so URL, credentials and model are all verified. */
suspend fun testConnection(settings: AgentProviderSettings): String {
val messages = buildJsonArray {
add(buildJsonObject {
put("role", "user")
put("content", "Connection test. Reply with OK only.")
})
}
return complete(settings, messages, emptyList()) {}.content
}

private suspend fun parseResponse(
reader: BufferedReader,
onText: suspend (String) -> Unit,
Expand Down Expand Up @@ -225,6 +238,7 @@ class OpenAICompatibleClient(
companion object {
private const val CONNECT_TIMEOUT_MS = 20_000
private const val READ_TIMEOUT_MS = 180_000
private const val STREAM_FRAME_MS = 45L
// Keep the source buffer fresh; visual pacing is handled independently on Android's VSync.
private const val STREAM_FRAME_MS = 24L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ object AgentExecutableTools {
boolean("restart_if_running", "Restart the VPN immediately so network-affecting changes take effect. Defaults to true.", required = false)),
tool("network_info", "Read active Android network, transports, DNS servers, routes, metering, and validation state.", READ_ONLY),
tool("logs_recent", "Read the tail of the newest saved mihomo log capture for diagnosis. Returns unavailable when no capture has been recorded.", READ_ONLY),
tool("app_exit_history", "Read Android's system process-exit history for the VPN process, including low-memory kills, Java/native crashes, ANRs, user stops, and signals. Android 11 or newer.", READ_ONLY),
tool("runtime_status", "Read VPN state, active profile, tunnel mode, traffic totals, proxy groups, and providers.", READ_ONLY),
tool("runtime_set_mode", "Set the current session tunnel mode (rule, global, direct, or script).", MEDIUM,
string("mode", "One of: rule, global, direct, script.")),
Expand Down
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ dependencies {
implementation(libs.google.material)
implementation(libs.quickie.bundled)
implementation(libs.androidx.activity.ktx)
implementation(libs.markwon.core)
testImplementation(kotlin("test"))
}

tasks.getByName("clean", type = Delete::class) {
Expand Down
Loading
Loading