percher 0.5.15 → 0.5.16

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.
@@ -119360,7 +119360,7 @@ async function renderError(stderr, error48) {
119360
119360
  // package.json
119361
119361
  var package_default = {
119362
119362
  name: "percher",
119363
- version: "0.5.15",
119363
+ version: "0.5.16",
119364
119364
  description: "CLI for the Percher hosting platform — publish apps with one command",
119365
119365
  type: "module",
119366
119366
  bin: {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "percher",
3
- "version": "0.5.15",
3
+ "version": "0.5.16",
4
4
  "description": "CLI for the Percher hosting platform \u2014 publish apps with one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,3 +30,62 @@ export async function complete(
30
30
  const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> };
31
31
  return data.choices?.[0]?.message?.content ?? "";
32
32
  }
33
+
34
+ /**
35
+ * Streaming variant of {@link complete} — calls `onToken` with each text delta
36
+ * as it arrives and resolves with the full text. Same auth + billing as
37
+ * `complete` (keyless, billed to the app owner). Use it for a typewriter UI:
38
+ *
39
+ * const full = await streamComplete("Write a haiku", (t) => { output.value += t; });
40
+ */
41
+ export async function streamComplete(
42
+ prompt: string,
43
+ onToken: (delta: string) => void,
44
+ opts?: { model?: string; maxTokens?: number },
45
+ ): Promise<string> {
46
+ if (!AI_URL) throw new Error("Managed inference isn't configured for this app.");
47
+ if (!pb.authStore.isValid) throw new Error("Sign in to use AI.");
48
+ const res = await fetch(AI_URL, {
49
+ method: "POST",
50
+ headers: { "content-type": "application/json", authorization: pb.authStore.token },
51
+ body: JSON.stringify({
52
+ model: opts?.model,
53
+ max_tokens: opts?.maxTokens ?? 512,
54
+ messages: [{ role: "user", content: prompt }],
55
+ stream: true,
56
+ }),
57
+ });
58
+ if (!res.ok || !res.body) throw new Error(`AI request failed (${res.status})`);
59
+
60
+ const reader = res.body.getReader();
61
+ const decoder = new TextDecoder();
62
+ let buffer = "";
63
+ let full = "";
64
+ for (;;) {
65
+ const { done, value } = await reader.read();
66
+ if (done) break;
67
+ buffer += decoder.decode(value, { stream: true });
68
+ // SSE events are separated by a blank line; parse each complete one.
69
+ let sep = buffer.indexOf("\n\n");
70
+ while (sep !== -1) {
71
+ const event = buffer.slice(0, sep);
72
+ buffer = buffer.slice(sep + 2);
73
+ sep = buffer.indexOf("\n\n");
74
+ const line = event.startsWith("data:") ? event.slice(5).trim() : "";
75
+ if (!line || line === "[DONE]") continue;
76
+ try {
77
+ const chunk = JSON.parse(line) as {
78
+ choices?: Array<{ delta?: { content?: string } }>;
79
+ };
80
+ const delta = chunk.choices?.[0]?.delta?.content ?? "";
81
+ if (delta) {
82
+ full += delta;
83
+ onToken(delta);
84
+ }
85
+ } catch {
86
+ /* ignore keep-alive / non-JSON lines */
87
+ }
88
+ }
89
+ }
90
+ return full;
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "percher",
3
- "version": "0.5.15",
3
+ "version": "0.5.16",
4
4
  "description": "CLI for the Percher hosting platform \u2014 publish apps with one command",
5
5
  "type": "module",
6
6
  "bin": {