mirror of
https://github.com/frido22/cellclaw
synced 2026-05-10 22:43:50 +00:00
Fix compilation errors and add Gradle wrapper
- Fix SSE streaming: replace inline processSSE with readLine loop to allow suspend emit() calls inside the flow - Remove @Serializable from CompletionRequest (ToolApiDefinition is not serializable) - Fix LocationTool type inference for suspendCancellableCoroutine - Add gradlew/gradlew.bat and gradle-wrapper.jar Build: assembleDebug passes, APK generated (20.7MB) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.BufferedReader
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -76,79 +76,94 @@ class AnthropicProvider @Inject constructor() : Provider {
|
||||
var stopReason = StopReason.END_TURN
|
||||
|
||||
try {
|
||||
reader.processSSE { event, data ->
|
||||
when (event) {
|
||||
"content_block_start" -> {
|
||||
val block = json.parseToJsonElement(data).jsonObject
|
||||
val contentBlock = block["content_block"]?.jsonObject ?: return@processSSE
|
||||
when (contentBlock["type"]?.jsonPrimitive?.content) {
|
||||
"tool_use" -> {
|
||||
// Flush any accumulated text
|
||||
if (currentText.isNotEmpty()) {
|
||||
contentBlocks.add(ContentBlock.Text(currentText.toString()))
|
||||
currentText = StringBuilder()
|
||||
var sseEvent = ""
|
||||
var sseData = StringBuilder()
|
||||
|
||||
while (true) {
|
||||
val line = reader.readLine() ?: break
|
||||
|
||||
when {
|
||||
line.startsWith("event: ") -> sseEvent = line.removePrefix("event: ").trim()
|
||||
line.startsWith("data: ") -> sseData.append(line.removePrefix("data: "))
|
||||
line.isBlank() -> {
|
||||
if (sseEvent.isNotEmpty() && sseData.isNotEmpty()) {
|
||||
val data = sseData.toString()
|
||||
when (sseEvent) {
|
||||
"content_block_start" -> {
|
||||
val block = json.parseToJsonElement(data).jsonObject
|
||||
val cb = block["content_block"]?.jsonObject
|
||||
if (cb != null && cb["type"]?.jsonPrimitive?.content == "tool_use") {
|
||||
if (currentText.isNotEmpty()) {
|
||||
contentBlocks.add(ContentBlock.Text(currentText.toString()))
|
||||
currentText = StringBuilder()
|
||||
}
|
||||
currentToolId = cb["id"]?.jsonPrimitive?.content ?: ""
|
||||
currentToolName = cb["name"]?.jsonPrimitive?.content ?: ""
|
||||
currentToolInput = StringBuilder()
|
||||
emit(StreamEvent.ToolUseStart(currentToolId, currentToolName))
|
||||
}
|
||||
}
|
||||
"content_block_delta" -> {
|
||||
val block = json.parseToJsonElement(data).jsonObject
|
||||
val delta = block["delta"]?.jsonObject
|
||||
if (delta != null) {
|
||||
when (delta["type"]?.jsonPrimitive?.content) {
|
||||
"text_delta" -> {
|
||||
val text = delta["text"]?.jsonPrimitive?.content ?: ""
|
||||
currentText.append(text)
|
||||
emit(StreamEvent.TextDelta(text))
|
||||
}
|
||||
"input_json_delta" -> {
|
||||
val partial = delta["partial_json"]?.jsonPrimitive?.content ?: ""
|
||||
currentToolInput.append(partial)
|
||||
emit(StreamEvent.ToolUseInputDelta(partial))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"content_block_stop" -> {
|
||||
if (currentToolName.isNotEmpty()) {
|
||||
val inputJson = try {
|
||||
json.parseToJsonElement(currentToolInput.toString()).jsonObject
|
||||
} catch (_: Exception) {
|
||||
JsonObject(emptyMap())
|
||||
}
|
||||
contentBlocks.add(
|
||||
ContentBlock.ToolUse(currentToolId, currentToolName, inputJson)
|
||||
)
|
||||
currentToolId = ""
|
||||
currentToolName = ""
|
||||
currentToolInput = StringBuilder()
|
||||
}
|
||||
}
|
||||
"message_delta" -> {
|
||||
val block = json.parseToJsonElement(data).jsonObject
|
||||
val delta = block["delta"]?.jsonObject
|
||||
val reason = delta?.get("stop_reason")?.jsonPrimitive?.content
|
||||
stopReason = when (reason) {
|
||||
"tool_use" -> StopReason.TOOL_USE
|
||||
"max_tokens" -> StopReason.MAX_TOKENS
|
||||
else -> StopReason.END_TURN
|
||||
}
|
||||
}
|
||||
"message_stop" -> {
|
||||
if (currentText.isNotEmpty()) {
|
||||
contentBlocks.add(ContentBlock.Text(currentText.toString()))
|
||||
}
|
||||
emit(StreamEvent.Complete(
|
||||
CompletionResponse(contentBlocks, stopReason)
|
||||
))
|
||||
}
|
||||
"error" -> {
|
||||
val block = json.parseToJsonElement(data).jsonObject
|
||||
val error = block["error"]?.jsonObject
|
||||
val message = error?.get("message")?.jsonPrimitive?.content ?: data
|
||||
emit(StreamEvent.Error(message))
|
||||
}
|
||||
currentToolId = contentBlock["id"]?.jsonPrimitive?.content ?: ""
|
||||
currentToolName = contentBlock["name"]?.jsonPrimitive?.content ?: ""
|
||||
currentToolInput = StringBuilder()
|
||||
emit(StreamEvent.ToolUseStart(currentToolId, currentToolName))
|
||||
}
|
||||
}
|
||||
}
|
||||
"content_block_delta" -> {
|
||||
val block = json.parseToJsonElement(data).jsonObject
|
||||
val delta = block["delta"]?.jsonObject ?: return@processSSE
|
||||
when (delta["type"]?.jsonPrimitive?.content) {
|
||||
"text_delta" -> {
|
||||
val text = delta["text"]?.jsonPrimitive?.content ?: ""
|
||||
currentText.append(text)
|
||||
emit(StreamEvent.TextDelta(text))
|
||||
}
|
||||
"input_json_delta" -> {
|
||||
val partial = delta["partial_json"]?.jsonPrimitive?.content ?: ""
|
||||
currentToolInput.append(partial)
|
||||
emit(StreamEvent.ToolUseInputDelta(partial))
|
||||
}
|
||||
}
|
||||
}
|
||||
"content_block_stop" -> {
|
||||
if (currentToolName.isNotEmpty()) {
|
||||
val inputJson = try {
|
||||
json.parseToJsonElement(currentToolInput.toString()).jsonObject
|
||||
} catch (_: Exception) {
|
||||
JsonObject(emptyMap())
|
||||
}
|
||||
contentBlocks.add(
|
||||
ContentBlock.ToolUse(currentToolId, currentToolName, inputJson)
|
||||
)
|
||||
currentToolId = ""
|
||||
currentToolName = ""
|
||||
currentToolInput = StringBuilder()
|
||||
}
|
||||
}
|
||||
"message_delta" -> {
|
||||
val block = json.parseToJsonElement(data).jsonObject
|
||||
val delta = block["delta"]?.jsonObject
|
||||
val reason = delta?.get("stop_reason")?.jsonPrimitive?.content
|
||||
stopReason = when (reason) {
|
||||
"tool_use" -> StopReason.TOOL_USE
|
||||
"max_tokens" -> StopReason.MAX_TOKENS
|
||||
else -> StopReason.END_TURN
|
||||
}
|
||||
}
|
||||
"message_stop" -> {
|
||||
if (currentText.isNotEmpty()) {
|
||||
contentBlocks.add(ContentBlock.Text(currentText.toString()))
|
||||
}
|
||||
emit(StreamEvent.Complete(
|
||||
CompletionResponse(contentBlocks, stopReason)
|
||||
))
|
||||
}
|
||||
"error" -> {
|
||||
val block = json.parseToJsonElement(data).jsonObject
|
||||
val error = block["error"]?.jsonObject
|
||||
val message = error?.get("message")?.jsonPrimitive?.content ?: data
|
||||
emit(StreamEvent.Error(message))
|
||||
sseEvent = ""
|
||||
sseData = StringBuilder()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,29 +293,6 @@ class AnthropicProvider @Inject constructor() : Provider {
|
||||
return CompletionResponse(blocks, stopReason, usage)
|
||||
}
|
||||
|
||||
private inline fun BufferedReader.processSSE(handler: (event: String, data: String) -> Unit) {
|
||||
var currentEvent = ""
|
||||
var currentData = StringBuilder()
|
||||
|
||||
forEachLine { line ->
|
||||
when {
|
||||
line.startsWith("event: ") -> {
|
||||
currentEvent = line.removePrefix("event: ").trim()
|
||||
}
|
||||
line.startsWith("data: ") -> {
|
||||
currentData.append(line.removePrefix("data: "))
|
||||
}
|
||||
line.isBlank() -> {
|
||||
if (currentEvent.isNotEmpty() && currentData.isNotEmpty()) {
|
||||
handler(currentEvent, currentData.toString())
|
||||
}
|
||||
currentEvent = ""
|
||||
currentData = StringBuilder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val API_URL = "https://api.anthropic.com/v1/messages"
|
||||
const val API_VERSION = "2023-06-01"
|
||||
|
||||
@@ -18,7 +18,6 @@ interface Provider {
|
||||
fun stream(request: CompletionRequest): Flow<StreamEvent>
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class CompletionRequest(
|
||||
val systemPrompt: String,
|
||||
val messages: List<Message>,
|
||||
|
||||
@@ -33,7 +33,7 @@ class LocationTool @Inject constructor(
|
||||
val client = LocationServices.getFusedLocationProviderClient(context)
|
||||
val cancellationToken = CancellationTokenSource()
|
||||
|
||||
val location = suspendCancellableCoroutine { cont ->
|
||||
val location = suspendCancellableCoroutine<android.location.Location?> { cont ->
|
||||
client.getCurrentLocation(
|
||||
Priority.PRIORITY_HIGH_ACCURACY,
|
||||
cancellationToken.token
|
||||
@@ -45,17 +45,20 @@ class LocationTool @Inject constructor(
|
||||
cont.invokeOnCancellation { cancellationToken.cancel() }
|
||||
} ?: return ToolResult.error("Could not get location")
|
||||
|
||||
val lat = location.latitude
|
||||
val lon = location.longitude
|
||||
|
||||
val result = buildJsonObject {
|
||||
put("latitude", location.latitude)
|
||||
put("longitude", location.longitude)
|
||||
put("latitude", lat)
|
||||
put("longitude", lon)
|
||||
put("accuracy_meters", location.accuracy.toDouble())
|
||||
location.altitude.let { put("altitude", it) }
|
||||
put("altitude", location.altitude)
|
||||
|
||||
if (geocode) {
|
||||
try {
|
||||
@Suppress("DEPRECATION")
|
||||
val geocoder = Geocoder(context, Locale.getDefault())
|
||||
val addresses = geocoder.getFromLocation(location.latitude, location.longitude, 1)
|
||||
val addresses = geocoder.getFromLocation(lat, lon, 1)
|
||||
addresses?.firstOrNull()?.let { addr ->
|
||||
put("address", buildJsonObject {
|
||||
put("street", addr.getAddressLine(0) ?: "")
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
66
gradlew
vendored
Normal file
66
gradlew
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
|
||||
##############################################################################
|
||||
## Gradle wrapper script for POSIX compatible systems
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
PRG="$0"
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME"
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH."
|
||||
fi
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$DEFAULT_JVM_OPTS \
|
||||
$JAVA_OPTS \
|
||||
$GRADLE_OPTS \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
60
gradlew.bat
vendored
Normal file
60
gradlew.bat
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem Licensed under the Apache License, Version 2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %OS%=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@exit /b %ERRORLEVEL%
|
||||
|
||||
:fail
|
||||
@exit /b 1
|
||||
Reference in New Issue
Block a user