@extentos/mcp-server 0.0.75 → 0.0.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/tools/data/codeExamples.d.ts +53 -0
  2. package/dist/tools/data/codeExamples.d.ts.map +1 -1
  3. package/dist/tools/data/codeExamples.js +254 -62
  4. package/dist/tools/data/codeExamples.js.map +1 -1
  5. package/dist/tools/definitions.d.ts.map +1 -1
  6. package/dist/tools/definitions.js +6 -8
  7. package/dist/tools/definitions.js.map +1 -1
  8. package/dist/tools/docs/index.d.ts.map +1 -1
  9. package/dist/tools/docs/index.js +16 -7
  10. package/dist/tools/docs/index.js.map +1 -1
  11. package/dist/tools/handlers/createSimulatorSession.d.ts.map +1 -1
  12. package/dist/tools/handlers/createSimulatorSession.js +12 -1
  13. package/dist/tools/handlers/createSimulatorSession.js.map +1 -1
  14. package/dist/tools/handlers/generateConnectionModule.d.ts.map +1 -1
  15. package/dist/tools/handlers/generateConnectionModule.js +20 -13
  16. package/dist/tools/handlers/generateConnectionModule.js.map +1 -1
  17. package/dist/tools/handlers/getCodeExample.d.ts.map +1 -1
  18. package/dist/tools/handlers/getCodeExample.js +11 -1
  19. package/dist/tools/handlers/getCodeExample.js.map +1 -1
  20. package/dist/tools/handlers/getCredentialGuide.js +57 -8
  21. package/dist/tools/handlers/getCredentialGuide.js.map +1 -1
  22. package/dist/tools/handlers/getEventLog.d.ts.map +1 -1
  23. package/dist/tools/handlers/getEventLog.js +8 -10
  24. package/dist/tools/handlers/getEventLog.js.map +1 -1
  25. package/dist/tools/handlers/inspectIntegration.d.ts.map +1 -1
  26. package/dist/tools/handlers/inspectIntegration.js +8 -3
  27. package/dist/tools/handlers/inspectIntegration.js.map +1 -1
  28. package/dist/tools/handlers/validateIntegration.d.ts.map +1 -1
  29. package/dist/tools/handlers/validateIntegration.js +9 -3
  30. package/dist/tools/handlers/validateIntegration.js.map +1 -1
  31. package/dist/tools/util/generatedHash.d.ts +24 -8
  32. package/dist/tools/util/generatedHash.d.ts.map +1 -1
  33. package/dist/tools/util/generatedHash.js +88 -11
  34. package/dist/tools/util/generatedHash.js.map +1 -1
  35. package/package.json +1 -1
  36. package/dist/tools/data/examplePatterns.d.ts +0 -4
  37. package/dist/tools/data/examplePatterns.d.ts.map +0 -1
  38. package/dist/tools/data/examplePatterns.js +0 -546
  39. package/dist/tools/data/examplePatterns.js.map +0 -1
  40. package/dist/tools/data/presetTriggers.d.ts +0 -4
  41. package/dist/tools/data/presetTriggers.d.ts.map +0 -1
  42. package/dist/tools/data/presetTriggers.js +0 -47
  43. package/dist/tools/data/presetTriggers.js.map +0 -1
  44. package/dist/tools/handlers/generateConsumer.d.ts +0 -3
  45. package/dist/tools/handlers/generateConsumer.d.ts.map +0 -1
  46. package/dist/tools/handlers/generateConsumer.js +0 -477
  47. package/dist/tools/handlers/generateConsumer.js.map +0 -1
  48. package/dist/tools/handlers/getExampleSpec.d.ts +0 -3
  49. package/dist/tools/handlers/getExampleSpec.d.ts.map +0 -1
  50. package/dist/tools/handlers/getExampleSpec.js +0 -50
  51. package/dist/tools/handlers/getExampleSpec.js.map +0 -1
  52. package/dist/tools/handlers/initSpec.d.ts +0 -3
  53. package/dist/tools/handlers/initSpec.d.ts.map +0 -1
  54. package/dist/tools/handlers/initSpec.js +0 -350
  55. package/dist/tools/handlers/initSpec.js.map +0 -1
  56. package/dist/tools/handlers/planIntegration.d.ts +0 -3
  57. package/dist/tools/handlers/planIntegration.d.ts.map +0 -1
  58. package/dist/tools/handlers/planIntegration.js +0 -293
  59. package/dist/tools/handlers/planIntegration.js.map +0 -1
  60. package/dist/tools/handlers/updateSpec.d.ts +0 -3
  61. package/dist/tools/handlers/updateSpec.d.ts.map +0 -1
  62. package/dist/tools/handlers/updateSpec.js +0 -579
  63. package/dist/tools/handlers/updateSpec.js.map +0 -1
  64. package/dist/tools/handlers/validateSpec.d.ts +0 -3
  65. package/dist/tools/handlers/validateSpec.d.ts.map +0 -1
  66. package/dist/tools/handlers/validateSpec.js +0 -33
  67. package/dist/tools/handlers/validateSpec.js.map +0 -1
  68. package/dist/tools/templates/callbackHandler.d.ts +0 -11
  69. package/dist/tools/templates/callbackHandler.d.ts.map +0 -1
  70. package/dist/tools/templates/callbackHandler.js +0 -215
  71. package/dist/tools/templates/callbackHandler.js.map +0 -1
  72. package/dist/tools/templates/streamConsumer.d.ts +0 -8
  73. package/dist/tools/templates/streamConsumer.d.ts.map +0 -1
  74. package/dist/tools/templates/streamConsumer.js +0 -322
  75. package/dist/tools/templates/streamConsumer.js.map +0 -1
  76. package/dist/tools/util/userCodeMerge.d.ts +0 -69
  77. package/dist/tools/util/userCodeMerge.d.ts.map +0 -1
  78. package/dist/tools/util/userCodeMerge.js +0 -168
  79. package/dist/tools/util/userCodeMerge.js.map +0 -1
  80. package/dist/tools/validation/validateSpec.d.ts +0 -22
  81. package/dist/tools/validation/validateSpec.d.ts.map +0 -1
  82. package/dist/tools/validation/validateSpec.js +0 -623
  83. package/dist/tools/validation/validateSpec.js.map +0 -1
@@ -1,3 +1,35 @@
1
+ /** Gradle dependency a pattern needs the host app to add. */
2
+ export interface RequiredGradleDep {
3
+ /** Configuration name as it appears in `dependencies { ... }`. */
4
+ configuration: "implementation" | "debugImplementation" | "testImplementation" | "androidTestImplementation";
5
+ /** Maven coordinate string, e.g. "com.squareup.okhttp3:okhttp:4.12.0". */
6
+ notation: string;
7
+ /** Which file the dep block lives in. */
8
+ appliedIn: "app/build.gradle.kts";
9
+ }
10
+ /** Gradle plugin a pattern needs the host app to declare. */
11
+ export interface RequiredGradlePlugin {
12
+ /** Plugin id, e.g. "org.jetbrains.kotlin.plugin.serialization". */
13
+ id: string;
14
+ /** Version string — only set on the root declaration. */
15
+ version?: string;
16
+ /** Which file the plugin block lives in. */
17
+ appliedIn: "root build.gradle.kts" | "app/build.gradle.kts";
18
+ /** When true, root declaration carries `apply false`; the app file
19
+ * declares the same plugin id with no version to actually apply it. */
20
+ applyFalse?: boolean;
21
+ /** Free-text caveat the agent should read before applying. */
22
+ note?: string;
23
+ }
24
+ /** Swift Package Manager dependency a pattern needs (iOS host). */
25
+ export interface RequiredSwiftPackage {
26
+ /** Repository URL. */
27
+ url: string;
28
+ /** Version spec, e.g. "1.2.0" used with `.upToNextMajor(from:)`. */
29
+ from: string;
30
+ /** Library products to add to the target. */
31
+ products: string[];
32
+ }
1
33
  export interface CodeExample {
2
34
  pattern: string;
3
35
  title: string;
@@ -9,6 +41,27 @@ export interface CodeExample {
9
41
  explanation: string;
10
42
  gotchas: string[];
11
43
  relatedFeatures: string[];
44
+ /**
45
+ * Extra Gradle / SPM deps the host app must add for this pattern's
46
+ * `code` block to compile. Omitted on patterns that need nothing
47
+ * beyond the SDK + stdlib. Surfaced verbatim by getCodeExample so
48
+ * agents can merge the union of deps from every pattern they use into
49
+ * the build files without scraping `code` comments (R2 F-R2-8).
50
+ */
51
+ requiredDependencies?: {
52
+ android?: RequiredGradleDep[];
53
+ iosSwiftPackages?: RequiredSwiftPackage[];
54
+ };
55
+ /**
56
+ * Gradle plugins this pattern needs. Plugins typically appear TWICE:
57
+ * once in the root `build.gradle.kts` plugins block (with `version` +
58
+ * `applyFalse: true`), once in `app/build.gradle.kts` plugins block
59
+ * (with no version) to actually take effect. iOS has no plugin
60
+ * concept; SPM handles its own bootstrapping.
61
+ */
62
+ requiredPlugins?: {
63
+ android?: RequiredGradlePlugin[];
64
+ };
12
65
  }
13
66
  export declare const CODE_EXAMPLES: Record<string, CodeExample>;
14
67
  export declare const CODE_EXAMPLE_PATTERNS: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"codeExamples.d.ts","sourceRoot":"","sources":["../../../src/tools/data/codeExamples.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAu1BD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAQrD,CAAC;AAEF,eAAO,MAAM,qBAAqB,UAAoC,CAAC"}
1
+ {"version":3,"file":"codeExamples.d.ts","sourceRoot":"","sources":["../../../src/tools/data/codeExamples.ts"],"names":[],"mappings":"AAeA,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,aAAa,EAAE,gBAAgB,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,2BAA2B,CAAC;IAC7G,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,SAAS,EAAE,sBAAsB,CAAC;CACnC;AAED,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,SAAS,EAAE,uBAAuB,GAAG,sBAAsB,CAAC;IAC5D;4EACwE;IACxE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,WAAW,oBAAoB;IACnC,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE;QACrB,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;QAC9B,gBAAgB,CAAC,EAAE,oBAAoB,EAAE,CAAC;KAC3C,CAAC;IACF;;;;;;OAMG;IACH,eAAe,CAAC,EAAE;QAChB,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAC;KAClC,CAAC;CACH;AAshCD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAQrD,CAAC;AAEF,eAAO,MAAM,qBAAqB,UAAoC,CAAC"}
@@ -10,6 +10,7 @@
10
10
  // app_callback, no triggers/blocks/actions DSL. Customer-side LLM
11
11
  // clients are referenced as `anthropic` / `vision` / `repo` without
12
12
  // importing — agents wire those to their existing backend.
13
+ import { VERSION_INFO } from "./version.js";
13
14
  const VOICE_QA_ASSISTANT = {
14
15
  pattern: "voice_qa_assistant",
15
16
  title: "Voice Q&A assistant (wake + question + LLM + speak)",
@@ -56,11 +57,19 @@ class CoachHandler(
56
57
  val question = recording.transcript.trim()
57
58
  if (question.isEmpty() || "stop" in question.lowercase()) break
58
59
 
59
- val answer = anthropic.ask(
60
+ // ask() returns LlmResult. Pattern-match so AuthFailure,
61
+ // NetworkError, and Empty each speak a distinct line — the
62
+ // F-R2-10 bug was collapsing all three into one message.
63
+ val spoken = when (val r = anthropic.ask(
60
64
  question = question,
61
65
  workoutHistory = workouts.recent(),
62
- )
63
- glasses.audio.speak(answer)
66
+ )) {
67
+ is LlmResult.Ok -> r.text
68
+ LlmResult.AuthFailure -> "Your Anthropic key isn't set up — add it to local.properties and rebuild."
69
+ LlmResult.NetworkError -> "I couldn't reach the AI. Try again."
70
+ LlmResult.Empty -> "I didn't get an answer. Try rephrasing."
71
+ }
72
+ glasses.audio.speak(spoken)
64
73
  }
65
74
  }
66
75
  }
@@ -105,12 +114,17 @@ class CoachHandler(
105
114
  let question = recording.transcript.trimmingCharacters(in: .whitespaces)
106
115
  if question.isEmpty || question.lowercased().contains("stop") { break }
107
116
 
108
- if let answer = try? await anthropic.ask(
109
- question: question,
110
- workoutHistory: workouts.recent()
111
- ) {
112
- _ = await glasses.audio.speak(answer)
117
+ // ask() returns LlmResult. Switch so AuthFailure, NetworkError,
118
+ // and Empty each speak a distinct line — the F-R2-10 bug was
119
+ // collapsing all three into one user-facing message.
120
+ let spoken: String
121
+ switch await anthropic.ask(question: question, workoutHistory: workouts.recent()) {
122
+ case .ok(let text): spoken = text
123
+ case .authFailure: spoken = "Your Anthropic key isn't set up — check Info.plist."
124
+ case .networkError: spoken = "I couldn't reach the AI. Try again."
125
+ case .empty: spoken = "I didn't get an answer. Try rephrasing."
113
126
  }
127
+ _ = await glasses.audio.speak(spoken)
114
128
  }
115
129
  }
116
130
 
@@ -130,6 +144,7 @@ class CoachHandler(
130
144
  "speak() blocks until done by default. If you want speak + listen-for-barge-in simultaneously, see the barge_in_speak pattern.",
131
145
  "BuildConfig API-key fields don't reflect rotations because kotlinc inlines them at compile time. Use resValue / R.string.X for Android secrets. See getCredentialGuide.",
132
146
  "AnthropicClient is a USER-PROVIDED stub — there's no first-party Anthropic Kotlin SDK. Use getCodeExample('byok_anthropic') for a paste-ready minimal OkHttp / URLSession wrapper around POST /v1/messages, and getCredentialGuide('anthropic') for the key-storage pattern. WorkoutRepository is your app's existing data layer — swap in whatever shape your app uses.",
147
+ "ask() returns LlmResult, not String. Pattern-match each failure mode (AuthFailure, NetworkError, Empty) into a distinct spoken line — flattening them is the R2 F-R2-10 finding (user can't distinguish 'fix your key' from 'AI didn't understand').",
133
148
  ],
134
149
  relatedFeatures: ["voice_command", "transcription_incremental", "record_audio"],
135
150
  };
@@ -252,12 +267,21 @@ class VisionHandler(
252
267
  }
253
268
  val mediaType = Photos.mediaTypeFromUri(uri) ?: "image/jpeg"
254
269
 
255
- val description = vision.describe(
270
+ // describe() returns LlmResult — pattern-match so each failure
271
+ // mode gets the right user-facing message. Folding all three
272
+ // into one "I couldn't describe that one." is the F-R2-10 bug
273
+ // (user can't distinguish "fix your key" from "AI was unsure").
274
+ val spoken = when (val r = vision.describe(
256
275
  imageBase64 = base64,
257
276
  mediaType = mediaType,
258
277
  prompt = "Describe this scene briefly, conversationally.",
259
- )
260
- glasses.audio.speak(description.ifBlank { "I couldn't describe that one." })
278
+ )) {
279
+ is LlmResult.Ok -> r.text
280
+ LlmResult.AuthFailure -> "Your Anthropic key isn't set up — add it to local.properties and rebuild."
281
+ LlmResult.NetworkError -> "I couldn't reach the vision service. Try again."
282
+ LlmResult.Empty -> "I couldn't describe that one. Try a different angle."
283
+ }
284
+ glasses.audio.speak(spoken)
261
285
  }
262
286
  }
263
287
 
@@ -294,14 +318,17 @@ class VisionHandler(
294
318
  // Decode via the bundled extension; pass the bytes to
295
319
  // your vision LLM however it expects them (base64, Data).
296
320
  let image = await photo.loadImage()
297
- let description = (try? await vision.describe(
298
- image: image,
299
- prompt: "Describe this scene briefly, conversationally."
300
- )) ?? ""
301
- let answer = description.isEmpty
302
- ? "I couldn't describe that one."
303
- : description
304
- _ = await glasses.audio.speak(answer)
321
+ // describe() returns LlmResult switch so each failure mode
322
+ // gets the right user-facing message. Folding all three into
323
+ // one "I couldn't describe that one." is the F-R2-10 bug.
324
+ let spoken: String
325
+ switch await vision.describe(image: image, prompt: "Describe this scene briefly, conversationally.") {
326
+ case .ok(let text): spoken = text
327
+ case .authFailure: spoken = "Your Anthropic key isn't set up — check Info.plist."
328
+ case .networkError: spoken = "I couldn't reach the vision service. Try again."
329
+ case .empty: spoken = "I couldn't describe that one. Try a different angle."
330
+ }
331
+ _ = await glasses.audio.speak(spoken)
305
332
  }
306
333
  }
307
334
 
@@ -316,6 +343,7 @@ class VisionHandler(
316
343
  "Android: Photos.loadBase64(uri) bridges both data: and file: URIs into bytes ready for Claude Vision / GPT-4V request bodies. iOS: `photo.loadImage()` (extension on Photo) returns a UIImage; for base64 you encode the JPEG/PNG data yourself via Data.base64EncodedString().",
317
344
  "Vision LLM response time is 2-8 seconds p95; the user is staring at the glasses waiting. Consider an earcon (`glasses.audio.earcon(EarconSound.START)`) at capture time so they know it worked.",
318
345
  "VisionClient is a USER-PROVIDED stub — see getCodeExample('byok_anthropic') for the canonical Anthropic Claude Vision wrapper (request shape: content blocks with type:image / source:base64 + type:text). The byok_anthropic pattern is paste-ready in both Kotlin and Swift.",
346
+ "describe() returns LlmResult, not String. Pattern-match (Kotlin `when` / Swift `switch`) so AuthFailure, NetworkError, and Empty each get a distinct user-facing line. The R2 dogfood showed users hearing 'I couldn't describe that one.' equally for 'API key not set' and 'AI was unsure' — the sealed type forces the agent to give each its own message.",
319
347
  ],
320
348
  relatedFeatures: ["capture_photo", "voice_command", "transcription_incremental"],
321
349
  };
@@ -555,7 +583,8 @@ const BYOK_ANTHROPIC = {
555
583
  title: "BYOK Anthropic Claude API client (text + Vision, OkHttp / URLSession)",
556
584
  description: "Minimal AnthropicClient that voice_qa_assistant and photo_describe_voice reference. Two methods: `ask(question, history)` for text-only Q&A, `describe(imageBase64, mediaType, prompt)` for Claude Vision (image content blocks). There is no first-party Anthropic Kotlin SDK; this is the canonical OkHttp + kotlinx.serialization wrapper around POST /v1/messages. iOS uses URLSession + Codable. Paste, then wire the API key through resValue (Android) / Info.plist (iOS) per getCredentialGuide.",
557
585
  code: {
558
- kotlin: `import kotlinx.coroutines.Dispatchers
586
+ kotlin: `import java.io.IOException
587
+ import kotlinx.coroutines.Dispatchers
559
588
  import kotlinx.coroutines.withContext
560
589
  import kotlinx.serialization.SerialName
561
590
  import kotlinx.serialization.Serializable
@@ -566,10 +595,42 @@ import okhttp3.OkHttpClient
566
595
  import okhttp3.Request
567
596
  import okhttp3.RequestBody.Companion.toRequestBody
568
597
 
569
- // build.gradle.kts:
570
- // implementation("com.squareup.okhttp3:okhttp:4.12.0")
571
- // implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
572
- // plugins { id("org.jetbrains.kotlin.plugin.serialization") }
598
+ // Required deps + plugins are exposed structurally on this example's
599
+ // response (getCodeExample.requiredDependencies / requiredPlugins) so
600
+ // the agent can merge them into build files without re-reading this
601
+ // comment. See the response for the exact configuration + appliedIn
602
+ // fields. R2 F-R2-8 — formerly the agent had to scrape gradle hints
603
+ // from inline comments and forget the root plugins {} entry, breaking
604
+ // the kotlinx-serialization compile.
605
+
606
+ /**
607
+ * Outcome of a single ask/describe call. Sealed so when(result) is
608
+ * exhaustively checked by the compiler — adding a new variant forces
609
+ * every handler to acknowledge the new case.
610
+ *
611
+ * The four variants distinguish failure modes that produce the SAME
612
+ * empty-string symptom if you collapse them, and that need DIFFERENT
613
+ * user-facing responses on a voice-glasses app:
614
+ * Ok — speak / display the text.
615
+ * AuthFailure — key missing, invalid, or no billing. Actionable by the
616
+ * dev (rotate / configure the key); not actionable by
617
+ * the user. UX: "Your Anthropic key isn't set up."
618
+ * NetworkError— transient (no internet, DNS, timeout, 429 rate-limit,
619
+ * 5xx server). UX: "Try again in a moment."
620
+ * Empty — 2xx with no text content. Usually means the model
621
+ * couldn't make sense of the input. UX: "I couldn't
622
+ * describe that one."
623
+ *
624
+ * R2 dogfood F-R2-10: returning bare String collapses all three failure
625
+ * modes into "" and the user hears the same recorded message for every
626
+ * one — they can't tell "fix your key" from "the AI didn't understand".
627
+ */
628
+ sealed interface LlmResult {
629
+ data class Ok(val text: String) : LlmResult
630
+ data object AuthFailure : LlmResult
631
+ data object NetworkError : LlmResult
632
+ data object Empty : LlmResult
633
+ }
573
634
 
574
635
  class AnthropicClient(
575
636
  private val apiKey: String,
@@ -614,7 +675,7 @@ class AnthropicClient(
614
675
 
615
676
  /** Text-only Q&A. Anthropic's content field accepts either a bare String or a
616
677
  * list of ContentBlock; we use the list shape so this client can also do Vision. */
617
- suspend fun ask(question: String, history: List<String> = emptyList()): String =
678
+ suspend fun ask(question: String, history: List<String> = emptyList()): LlmResult =
618
679
  post(
619
680
  messages = buildList {
620
681
  for (turn in history) {
@@ -632,7 +693,7 @@ class AnthropicClient(
632
693
  mediaType: String,
633
694
  prompt: String,
634
695
  system: String? = null,
635
- ): String = post(
696
+ ): LlmResult = post(
636
697
  messages = listOf(
637
698
  Message(
638
699
  "user",
@@ -647,9 +708,9 @@ class AnthropicClient(
647
708
 
648
709
  // ---- Transport ----
649
710
 
650
- private suspend fun post(messages: List<Message>, system: String? = null): String =
711
+ private suspend fun post(messages: List<Message>, system: String? = null): LlmResult =
651
712
  withContext(Dispatchers.IO) {
652
- if (apiKey.isBlank()) return@withContext ""
713
+ if (apiKey.isBlank()) return@withContext LlmResult.AuthFailure
653
714
  val body = json.encodeToString(
654
715
  Req.serializer(),
655
716
  Req(model = model, maxTokens = maxTokens, messages = messages, system = system),
@@ -661,14 +722,35 @@ class AnthropicClient(
661
722
  .header("content-type", "application/json")
662
723
  .post(body.toRequestBody("application/json".toMediaType()))
663
724
  .build()
664
- client.newCall(request).execute().use { response ->
665
- val payload = response.body?.string() ?: return@withContext ""
666
- if (!response.isSuccessful) {
667
- android.util.Log.w("AnthropicClient", "HTTP \${response.code}: \${payload.take(500)}")
668
- return@withContext ""
725
+ try {
726
+ client.newCall(request).execute().use { response ->
727
+ val payload = response.body?.string().orEmpty()
728
+ when {
729
+ // 401/403: invalid / missing key, no billing on the
730
+ // account. Caller fixes config; user can't.
731
+ response.code == 401 || response.code == 403 -> {
732
+ android.util.Log.w("AnthropicClient", "Auth failure (\${response.code}): \${payload.take(500)}")
733
+ LlmResult.AuthFailure
734
+ }
735
+ // 429 + 5xx + any other non-2xx: transient from the
736
+ // app's perspective. Retry-with-backoff is the right
737
+ // strategy; the user-facing message is "try again".
738
+ !response.isSuccessful -> {
739
+ android.util.Log.w("AnthropicClient", "HTTP \${response.code}: \${payload.take(500)}")
740
+ LlmResult.NetworkError
741
+ }
742
+ else -> {
743
+ val text = json.decodeFromString(Res.serializer(), payload)
744
+ .content.firstOrNull { it.type == "text" }?.text.orEmpty().trim()
745
+ if (text.isEmpty()) LlmResult.Empty else LlmResult.Ok(text)
746
+ }
747
+ }
669
748
  }
670
- json.decodeFromString(Res.serializer(), payload)
671
- .content.firstOrNull { it.type == "text" }?.text.orEmpty().trim()
749
+ } catch (e: IOException) {
750
+ // No HTTP exchange happened — DNS failure, no internet,
751
+ // socket timeout. Same UX as a transient 5xx.
752
+ android.util.Log.w("AnthropicClient", "Network error: \${e.message}")
753
+ LlmResult.NetworkError
672
754
  }
673
755
  }
674
756
  }
@@ -676,13 +758,20 @@ class AnthropicClient(
676
758
  // Wire-up — your handler retrieves the API key from the resource string
677
759
  // emitted by your build.gradle.kts (see getCredentialGuide('anthropic')
678
760
  // for the resValue pattern), then passes it to a single client instance
679
- // held at Application scope:
761
+ // held at Application scope. Both ask() and describe() return LlmResult;
762
+ // pattern-match to give the user the right message per failure mode.
680
763
  //
681
764
  // val apiKey = context.getString(R.string.anthropic_api_key)
682
765
  // val anthropic = AnthropicClient(apiKey)
683
766
  //
684
767
  // // text:
685
- // val answer = anthropic.ask("How many bench reps did I do today?")
768
+ // val spoken = when (val r = anthropic.ask("How many bench reps today?")) {
769
+ // is LlmResult.Ok -> r.text
770
+ // LlmResult.AuthFailure -> "Your Anthropic key isn't set up — add it to local.properties and rebuild."
771
+ // LlmResult.NetworkError -> "I couldn't reach the AI. Try again."
772
+ // LlmResult.Empty -> "I didn't get an answer. Try rephrasing."
773
+ // }
774
+ // glasses.audio.speak(spoken)
686
775
  //
687
776
  // // vision (paired with capture_photo + Photos helpers):
688
777
  // val photo = glasses.camera.capturePhoto(
@@ -691,9 +780,37 @@ class AnthropicClient(
691
780
  // val uri = photo.uri ?: return
692
781
  // val b64 = Photos.loadBase64(uri) ?: return
693
782
  // val mt = Photos.mediaTypeFromUri(uri) ?: "image/jpeg"
694
- // val answer = anthropic.describe(b64, mt, "What card is showing?")`,
783
+ // val result = anthropic.describe(b64, mt, "What card is showing?")
784
+ // // when(result) { is LlmResult.Ok -> result.text; ...other cases... }`,
695
785
  swift: `import Foundation
696
786
 
787
+ /// Outcome of a single ask/describe call. Sealed via Swift enum so a
788
+ /// \`switch result\` on this is exhaustively checked — adding a new
789
+ /// case forces every handler to acknowledge it.
790
+ ///
791
+ /// The four cases distinguish failure modes that produce the SAME
792
+ /// empty-string symptom if you flatten them, and that need DIFFERENT
793
+ /// user-facing responses on a voice-glasses app:
794
+ /// .ok — speak / display the text.
795
+ /// .authFailure — key missing, invalid, or no billing. Actionable by
796
+ /// the dev; not by the user. UX: "Your Anthropic key
797
+ /// isn't set up."
798
+ /// .networkError— transient (no internet, DNS, timeout, 429, 5xx).
799
+ /// UX: "Try again in a moment."
800
+ /// .empty — 2xx with no text content. Usually means the model
801
+ /// couldn't make sense of the input. UX: "I couldn't
802
+ /// describe that one."
803
+ ///
804
+ /// R2 dogfood F-R2-10: throwing/empty-string semantics collapsed all
805
+ /// three failure modes into one, so the user heard the same recorded
806
+ /// message regardless of cause.
807
+ enum LlmResult {
808
+ case ok(String)
809
+ case authFailure
810
+ case networkError
811
+ case empty
812
+ }
813
+
697
814
  actor AnthropicClient {
698
815
  private let apiKey: String
699
816
  private let model: String
@@ -749,13 +866,13 @@ actor AnthropicClient {
749
866
 
750
867
  // ---- Public API ----
751
868
 
752
- /// Text-only Q&A.
753
- func ask(question: String, history: [String] = []) async throws -> String {
869
+ /// Text-only Q&A. Non-throwing: any error becomes an LlmResult case.
870
+ func ask(question: String, history: [String] = []) async -> LlmResult {
754
871
  var messages = history.map {
755
872
  Message(role: "user", content: [.text($0)])
756
873
  }
757
874
  messages.append(Message(role: "user", content: [.text(question)]))
758
- return try await post(messages: messages, system: nil)
875
+ return await post(messages: messages, system: nil)
759
876
  }
760
877
 
761
878
  /// Vision: one image (base64) + a text prompt. Source the imageBase64 +
@@ -766,7 +883,7 @@ actor AnthropicClient {
766
883
  mediaType: String,
767
884
  prompt: String,
768
885
  system: String? = nil,
769
- ) async throws -> String {
886
+ ) async -> LlmResult {
770
887
  let messages = [
771
888
  Message(
772
889
  role: "user",
@@ -776,45 +893,82 @@ actor AnthropicClient {
776
893
  ],
777
894
  ),
778
895
  ]
779
- return try await post(messages: messages, system: system)
896
+ return await post(messages: messages, system: system)
780
897
  }
781
898
 
782
899
  // ---- Transport ----
783
900
 
784
- private func post(messages: [Message], system: String?) async throws -> String {
785
- guard !apiKey.isEmpty else { return "" }
901
+ private func post(messages: [Message], system: String?) async -> LlmResult {
902
+ guard !apiKey.isEmpty else { return .authFailure }
786
903
  var request = URLRequest(url: endpoint)
787
904
  request.httpMethod = "POST"
788
- request.httpBody = try JSONEncoder().encode(
789
- Req(model: model, max_tokens: maxTokens, messages: messages, system: system)
790
- )
905
+ do {
906
+ request.httpBody = try JSONEncoder().encode(
907
+ Req(model: model, max_tokens: maxTokens, messages: messages, system: system)
908
+ )
909
+ } catch {
910
+ // Encoding error — typically a programming bug, not a network
911
+ // condition. Surface as networkError since it's not auth and
912
+ // not "the model returned nothing"; the handler will retry.
913
+ return .networkError
914
+ }
791
915
  request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
792
916
  request.setValue("2023-06-01", forHTTPHeaderField: "anthropic-version")
793
917
  request.setValue("application/json", forHTTPHeaderField: "content-type")
794
918
 
795
- let (data, response) = try await URLSession.shared.data(for: request)
796
- if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) {
797
- let body = String(data: data, encoding: .utf8) ?? "HTTP \\(http.statusCode)"
798
- throw NSError(
799
- domain: "Anthropic",
800
- code: http.statusCode,
801
- userInfo: [NSLocalizedDescriptionKey: body]
802
- )
919
+ let data: Data
920
+ let response: URLResponse
921
+ do {
922
+ (data, response) = try await URLSession.shared.data(for: request)
923
+ } catch {
924
+ // No HTTP exchange — DNS / no internet / timeout. Same UX
925
+ // as a transient 5xx.
926
+ return .networkError
927
+ }
928
+
929
+ if let http = response as? HTTPURLResponse {
930
+ switch http.statusCode {
931
+ case 401, 403:
932
+ return .authFailure
933
+ case 200..<300:
934
+ break
935
+ default:
936
+ // 429 + 5xx + everything else non-2xx → retryable.
937
+ return .networkError
938
+ }
939
+ }
940
+
941
+ do {
942
+ let decoded = try JSONDecoder().decode(Res.self, from: data)
943
+ let text = decoded.content.first(where: { $0.type == "text" })?.text ?? ""
944
+ return text.isEmpty ? .empty : .ok(text)
945
+ } catch {
946
+ // 2xx with an undecodable body — treat as empty rather than
947
+ // network so the user-facing message reflects "got something
948
+ // back, didn't understand it" rather than "couldn't reach".
949
+ return .empty
803
950
  }
804
- let decoded = try JSONDecoder().decode(Res.self, from: data)
805
- return decoded.content.first(where: { $0.type == "text" })?.text ?? ""
806
951
  }
807
952
  }
808
953
 
809
954
  // Wire-up — your handler reads the API key from Info.plist (which gets
810
955
  // it via xcconfig / env var — see getCredentialGuide for the storage
811
- // pattern), then passes it to a single actor instance:
956
+ // pattern), then passes it to a single actor instance. Both ask() and
957
+ // describe() return LlmResult; switch on it to give the user the right
958
+ // message per failure mode.
812
959
  //
813
960
  // let key = Bundle.main.object(forInfoDictionaryKey: "ANTHROPIC_API_KEY") as? String ?? ""
814
961
  // let anthropic = AnthropicClient(apiKey: key)
815
962
  //
816
963
  // // text:
817
- // let answer = try await anthropic.ask(question: "How many bench reps today?")
964
+ // let spoken: String
965
+ // switch await anthropic.ask(question: "How many bench reps today?") {
966
+ // case .ok(let text): spoken = text
967
+ // case .authFailure: spoken = "Your Anthropic key isn't set up — check Info.plist."
968
+ // case .networkError: spoken = "I couldn't reach the AI. Try again."
969
+ // case .empty: spoken = "I didn't get an answer. Try rephrasing."
970
+ // }
971
+ // _ = await glasses.audio.speak(spoken)
818
972
  //
819
973
  // // vision (paired with capture_photo + photo.loadImage helpers):
820
974
  // let result = await glasses.camera.capturePhoto(
@@ -824,11 +978,12 @@ actor AnthropicClient {
824
978
  // guard let image = await photo.loadImage(),
825
979
  // let jpeg = image.jpegData(compressionQuality: 0.85) else { return }
826
980
  // let b64 = jpeg.base64EncodedString()
827
- // let answer = try await anthropic.describe(
981
+ // let answer = await anthropic.describe(
828
982
  // imageBase64: b64,
829
983
  // mediaType: "image/jpeg",
830
984
  // prompt: "What's the next blackjack move?"
831
- // )`,
985
+ // )
986
+ // // switch answer { case .ok(let t): ...; case .authFailure: ...; ... }`,
832
987
  },
833
988
  explanation: "Minimal direct client supporting BOTH text Q&A (`ask`) and Claude Vision (`describe`). There's no first-party Anthropic Kotlin SDK; this is the wire-protocol wrapper voice_qa_assistant and photo_describe_voice patterns reference as `AnthropicClient`. Kotlin uses OkHttp + kotlinx-serialization with a `JsonClassDiscriminator(\"type\")` sealed ContentBlock that mirrors Anthropic's polymorphic content shape (text + image). Swift uses URLSession + Codable with manual encode/decode for the same polymorphism. The customer holds one instance at Application scope and passes it to handlers via DI.",
834
989
  gotchas: [
@@ -841,6 +996,43 @@ actor AnthropicClient {
841
996
  "Vision LLM response p95 is 2-8 seconds. Play an earcon (`glasses.audio.earcon(EarconSound.START)`) BEFORE the call so the user knows the request fired.",
842
997
  ],
843
998
  relatedFeatures: ["capture_photo"],
999
+ requiredDependencies: {
1000
+ android: [
1001
+ {
1002
+ configuration: "implementation",
1003
+ notation: "com.squareup.okhttp3:okhttp:4.12.0",
1004
+ appliedIn: "app/build.gradle.kts",
1005
+ },
1006
+ {
1007
+ configuration: "implementation",
1008
+ notation: "org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3",
1009
+ appliedIn: "app/build.gradle.kts",
1010
+ },
1011
+ ],
1012
+ // iOS uses URLSession + Codable from the standard library — no
1013
+ // extra SPM dependency required.
1014
+ iosSwiftPackages: [],
1015
+ },
1016
+ requiredPlugins: {
1017
+ android: [
1018
+ // The serialization plugin needs BOTH declarations to take effect:
1019
+ // version+applyFalse in the root, version-less in the app file.
1020
+ // Forgetting the root entry yields "Plugin not found"; forgetting
1021
+ // the app entry yields a compile failure on @Serializable.
1022
+ {
1023
+ id: "org.jetbrains.kotlin.plugin.serialization",
1024
+ version: VERSION_INFO.android.kotlinVersion,
1025
+ appliedIn: "root build.gradle.kts",
1026
+ applyFalse: true,
1027
+ note: "Version MUST match the kotlin.android plugin version (both come from the Kotlin distribution).",
1028
+ },
1029
+ {
1030
+ id: "org.jetbrains.kotlin.plugin.serialization",
1031
+ appliedIn: "app/build.gradle.kts",
1032
+ note: "No version here — inherits from the root declaration. Sits alongside id(\"com.android.application\") and id(\"org.jetbrains.kotlin.android\").",
1033
+ },
1034
+ ],
1035
+ },
844
1036
  };
845
1037
  export const CODE_EXAMPLES = {
846
1038
  voice_qa_assistant: VOICE_QA_ASSISTANT,
@@ -1 +1 @@
1
- {"version":3,"file":"codeExamples.js","sourceRoot":"","sources":["../../../src/tools/data/codeExamples.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"codeExamples.js","sourceRoot":"","sources":["../../../src/tools/data/codeExamples.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/tools/definitions.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IAMpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,oBAAoB,EAAE,KAAK,CAAC;KAC7B,CAAC;CACH;AAMD,eAAO,MAAM,eAAe,EAAE,OAAO,EAwXpC,CAAC;AAEF,eAAO,MAAM,SAAS,QAAyB,CAAC;AAShD,wBAAgB,iCAAiC,CAC/C,IAAI,GAAE,SAAS,OAAO,EAAoB,GACzC,IAAI,CAiBN"}
1
+ {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/tools/definitions.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IAMpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,oBAAoB,EAAE,KAAK,CAAC;KAC7B,CAAC;CACH;AAMD,eAAO,MAAM,eAAe,EAAE,OAAO,EAsXpC,CAAC;AAEF,eAAO,MAAM,SAAS,QAAyB,CAAC;AAShD,wBAAgB,iCAAiC,CAC/C,IAAI,GAAE,SAAS,OAAO,EAAoB,GACzC,IAAI,CAiBN"}
@@ -234,7 +234,7 @@ export const toolDefinitions = [
234
234
  },
235
235
  {
236
236
  name: "getEventLog",
237
- description: "Fetch structured event trace inside a simulator session. Primary debugging tool for *why is my handler not seeing what I expect* — transcripts not arriving, photo capture failing, toggle changes not propagating, speak getting cut off, connection dropping. Events are grouped by capability layer (transport / audio / camera / speak / toggle / stream / system) so you can filter to just the surface you're investigating. USE to diagnose which capability primitive is misbehaving. DON'T USE for static configuration checking (use validateIntegration) or for live session phase (use getSimulatorStatus).",
237
+ description: "Fetch structured event trace inside a simulator session. Primary debugging tool for *why is my handler not seeing what I expect* — transcripts not arriving, photo capture failing, toggle changes not propagating, speak getting cut off, connection dropping. Events are grouped into six chips (errors / voice / camera / ai / lifecycle / custom) one chip per event, with `errors` absorbing every severity≥warn row regardless of modality. To see e.g. voice activity plus voice errors, fetch the chips separately and union them. USE to diagnose which capability primitive is misbehaving. DON'T USE for static configuration checking (use validateIntegration) or for live session phase (use getSimulatorStatus). **Scope (R2 F-R2-12):** captures only the simulator's WebSocket relay — transport + SDK primitives (audio, camera, speak, toggles, voice triggers, runtime events). Customer-side direct-HTTP calls (BYOK Anthropic / OpenAI / Gemini / etc.) NEVER traverse the wire and so are invisible here — those failures surface in logcat (Android) or OSLog (iOS). When debugging a flow that mixes SDK primitives with external APIs (e.g. capture_photo → vision LLM → speak), combine getEventLog with the platform log surface; a getEventLog showing capture_photo + photo_result + speak with nothing in between is the signature of a silent BYOK failure.",
238
238
  inputSchema: {
239
239
  type: "object",
240
240
  properties: {
@@ -244,15 +244,13 @@ export const toolDefinitions = [
244
244
  enum: [
245
245
  "all",
246
246
  "errors",
247
- "transport",
248
- "audio",
247
+ "voice",
249
248
  "camera",
250
- "speak",
251
- "toggles",
252
- "streams",
253
- "system",
249
+ "ai",
250
+ "lifecycle",
251
+ "custom",
254
252
  ],
255
- description: "Layer to filter the event stream by. 'all' (default) returns every event; 'errors' returns only severity≥error; the remaining values scope to one capability layer.",
253
+ description: "Chip to filter the event stream by. 'all' (default) returns every event. 'errors' returns severity≥warn events across all modalities. 'voice' / 'camera' / 'ai' cover hardware/integration capabilities (severity=info only — failures land under 'errors'). 'lifecycle' covers connection / session / pairing / hardware / runtime state. 'custom' is the open-world catch-all for app-emitted event types the platform did not anticipate.",
256
254
  },
257
255
  limit: { type: "integer", minimum: 1 },
258
256
  since: { type: "string" },