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.
package/dist/bin/percher.js
CHANGED
|
@@ -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.
|
|
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
|
@@ -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
|
+
}
|