fastapi-poe
Advanced tools
Comparing version 0.2.0 to 0.2.1
113
dist/bin.js
@@ -1,36 +0,42 @@ | ||
import { Command as i } from "commander"; | ||
import { confirm as m, password as p } from "@inquirer/prompts"; | ||
import t from "node:path"; | ||
import { access as d, cp as u, writeFile as n, rename as g, readFile as w } from "node:fs/promises"; | ||
import { fileURLToPath as f } from "node:url"; | ||
import o from "chalk"; | ||
const v = "0.2.0", h = t.resolve( | ||
t.dirname(f(import.meta.url)), | ||
import { Command } from 'commander'; | ||
import { confirm, password } from '@inquirer/prompts'; | ||
import path from 'node:path'; | ||
import { access, cp, writeFile, rename, readFile } from 'node:fs/promises'; | ||
import { fileURLToPath } from 'node:url'; | ||
import chalk from 'chalk'; | ||
const version = "0.2.1"; | ||
const ROOT_PATH = path.resolve( | ||
path.dirname(fileURLToPath(import.meta.url)), | ||
".." | ||
), y = async (e) => d(e).then(() => !0).catch(() => !1); | ||
async function P(e) { | ||
const r = t.resolve(h, "./packages/poe-bot-template"); | ||
await u(r, e.distPath, { | ||
recursive: !0, | ||
force: !0, | ||
dereference: !0, | ||
filter: (a) => t.basename(a) !== "node_modules" | ||
}), await n( | ||
t.resolve(e.distPath, ".dev.vars"), | ||
`ACCESS_KEY="${e.accessKey}"` | ||
); | ||
const pathExists = async (path2) => access(path2).then(() => true).catch(() => false); | ||
async function init(options) { | ||
const srcPath = path.resolve(ROOT_PATH, "./packages/poe-bot-template"); | ||
await cp(srcPath, options.distPath, { | ||
recursive: true, | ||
force: true, | ||
dereference: true, | ||
filter: (source) => path.basename(source) !== "node_modules" | ||
}); | ||
await writeFile( | ||
path.resolve(options.distPath, ".dev.vars"), | ||
`ACCESS_KEY="${options.accessKey}"` | ||
); | ||
const s = async (a, c) => { | ||
const l = await w(a, "utf-8"); | ||
await n(a, c(l)); | ||
const replace = async (fsPath, content) => { | ||
const file = await readFile(fsPath, "utf-8"); | ||
await writeFile(fsPath, content(file)); | ||
}; | ||
await Promise.all( | ||
["package.json", "wrangler.toml", "src/index.ts"].map( | ||
(a) => s( | ||
t.resolve(e.distPath, a), | ||
(c) => c.replace("poe-bot-template", e.name) | ||
(it) => replace( | ||
path.resolve(options.distPath, it), | ||
(c) => c.replace("poe-bot-template", options.name) | ||
) | ||
).concat([ | ||
g( | ||
t.resolve(e.distPath, "_.gitignore"), | ||
t.resolve(e.distPath, ".gitignore") | ||
rename( | ||
path.resolve(options.distPath, "_.gitignore"), | ||
path.resolve(options.distPath, ".gitignore") | ||
) | ||
@@ -40,24 +46,34 @@ ]) | ||
} | ||
new i().addCommand( | ||
new i("init").arguments("<project-name>").action(async (e) => { | ||
const r = t.resolve(e); | ||
if (await y(r) && !await m({ | ||
message: "Directory already exists, do you want to overwrite it?", | ||
default: !1 | ||
})) | ||
return; | ||
const s = await p({ | ||
new Command().addCommand( | ||
new Command("init").arguments("<project-name>").action(async (name) => { | ||
const distPath = path.resolve(name); | ||
if (await pathExists(distPath)) { | ||
const isOverwrite = await confirm({ | ||
message: "Directory already exists, do you want to overwrite it?", | ||
default: false | ||
}); | ||
if (!isOverwrite) { | ||
return; | ||
} | ||
} | ||
const accessKey = await password({ | ||
message: "Input Poe Server Bot Access key", | ||
mask: "*" | ||
}); | ||
await P({ | ||
name: e, | ||
accessKey: s, | ||
distPath: r | ||
}), console.log(` | ||
✨ PoeAI server bot project create success! | ||
`), console.log("Next steps:"), console.log(` 1. ${o.blue(`cd ${e}`)}`), console.log(` 2. ${o.blue("pnpm i")}`), console.log(` 3. ${o.blue("pnpm dev")}`), console.log(` 4. ${o.blue("pnpm run deploy")}`), console.log( | ||
` 5. ${o.blue( | ||
`echo ${o.bgRed( | ||
s | ||
await init({ | ||
name, | ||
accessKey, | ||
distPath | ||
}); | ||
console.log("\n✨ PoeAI server bot project create success!\n"); | ||
console.log("Next steps:"); | ||
console.log(` 1. ${chalk.blue(`cd ${name}`)}`); | ||
console.log(` 2. ${chalk.blue("pnpm i")}`); | ||
console.log(` 3. ${chalk.blue("pnpm dev")}`); | ||
console.log(` 4. ${chalk.blue("pnpm run deploy")}`); | ||
console.log( | ||
` 5. ${chalk.blue( | ||
`echo ${chalk.bgRed( | ||
accessKey | ||
)} | pnpm wrangler secret put ACCESS_KEY` | ||
@@ -67,2 +83,3 @@ )}` | ||
}) | ||
).version(v).parse(); | ||
).version(version).parse(); | ||
//# sourceMappingURL=bin.js.map |
@@ -1,66 +0,89 @@ | ||
import { streamSSE as f } from "hono/streaming"; | ||
const h = "1.0"; | ||
class w extends Error { | ||
constructor(r) { | ||
super(r), this.name = "BotError"; | ||
import { streamSSE } from 'hono/streaming'; | ||
const PROTOCOL_VERSION = "1.0"; | ||
class BotError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "BotError"; | ||
} | ||
} | ||
async function p(e, r = "", o = "https://api.poe.com/bot/fetch_settings") { | ||
const t = await fetch( | ||
`${o}/${e}/${r}/${h}`, | ||
async function syncBotSettings(botName, accessKey = "", baseUrl = "https://api.poe.com/bot/fetch_settings") { | ||
const resp = await fetch( | ||
`${baseUrl}/${botName}/${accessKey}/${PROTOCOL_VERSION}`, | ||
{ | ||
method: "post" | ||
} | ||
), n = await t.text(); | ||
if (!t.ok) | ||
throw new w(`Error fetching settings for bot ${e}: ${n}`); | ||
console.log(n); | ||
); | ||
const text = await resp.text(); | ||
if (!resp.ok) { | ||
throw new BotError(`Error fetching settings for bot ${botName}: ${text}`); | ||
} | ||
console.log(text); | ||
} | ||
function S() { | ||
let e = ""; | ||
function sseTransformStream() { | ||
let buffer = ""; | ||
return new TransformStream({ | ||
transform(r, o) { | ||
if (e += r.trim(), e.endsWith('"}') || e.endsWith("{}")) { | ||
const t = e.match(/event:\s*(\w+)/), n = e.match(/data:\s*({.*})/); | ||
if (t && n) { | ||
const s = t[1], a = n[1]; | ||
if (s === "text" && a !== "{}") | ||
o.enqueue({ | ||
event: s, | ||
data: JSON.parse(a) | ||
transform(chunk, controller) { | ||
buffer += chunk; | ||
const textRegexp = /^event: text[\r\n]+data: ({[\s\S]*?})/; | ||
const doneRegexp = /^event: done[\r\n]+data: ({})/; | ||
while (buffer) { | ||
buffer = buffer.trimStart(); | ||
if (textRegexp.test(buffer)) { | ||
const match = buffer.match(textRegexp); | ||
if (match) { | ||
controller.enqueue({ | ||
event: "text", | ||
data: JSON.parse(match[1]) | ||
}); | ||
else if (s === "done") { | ||
o.terminate(); | ||
buffer = buffer.replace(match[0], "").trimStart(); | ||
} | ||
} else if (doneRegexp.test(buffer)) { | ||
const match = buffer.match(doneRegexp); | ||
if (match) { | ||
buffer = buffer.replace(match[0], "").trimStart(); | ||
controller.terminate(); | ||
return; | ||
} | ||
} else if (/^ping$/m.test(buffer)) { | ||
const match = buffer.match(/^ping$/m); | ||
if (match) { | ||
buffer = buffer.replace(match[0], "").trimStart(); | ||
} | ||
} else if (buffer.trim() === ": ping") { | ||
buffer = buffer.replace(": ping", "").trimStart(); | ||
} else { | ||
return; | ||
} | ||
e = ""; | ||
} | ||
}, | ||
flush() { | ||
e.trim() && console.warn("Unprocessed data in buffer:", e); | ||
if (buffer.trim()) { | ||
console.warn("Unprocessed data in buffer:", buffer); | ||
} | ||
} | ||
}); | ||
} | ||
async function* m(e, r, o) { | ||
const n = `https://api.poe.com/bot/${r}`, s = await fetch(n, { | ||
async function* streamRequest(request, botName, accessKey) { | ||
const response = await fetch(`https://api.poe.com/bot/${botName}`, { | ||
method: "post", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Authorization: `Bearer ${o}`, | ||
Authorization: `Bearer ${accessKey}`, | ||
Accept: "text/event-stream", | ||
"Cache-Control": "no-cache" | ||
}, | ||
body: JSON.stringify(e) | ||
body: JSON.stringify(request) | ||
}); | ||
if (!s.ok || !s.body) | ||
throw new Error(`HTTP error! status: ${s.status}`); | ||
const a = s.body.pipeThrough(new TextDecoderStream()).pipeThrough(S()).getReader(); | ||
let c = await a.read(); | ||
for (; !c.done; ) { | ||
const i = c.value; | ||
let d = ""; | ||
switch (i.event) { | ||
if (!response.ok || !response.body) { | ||
throw new Error(`HTTP error! status: ${response.status}`); | ||
} | ||
const reader = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(sseTransformStream()).getReader(); | ||
let chunk = await reader.read(); | ||
while (!chunk.done) { | ||
const parsed = chunk.value; | ||
let text = ""; | ||
switch (parsed.event) { | ||
case "text": | ||
d = i.data.text; | ||
text = parsed.data.text; | ||
break; | ||
@@ -70,56 +93,68 @@ case "done": | ||
default: | ||
throw new Error(`Unexpected event: ${i.event}`); | ||
throw new Error(`Unexpected event: ${parsed.event}`); | ||
} | ||
yield { text: d }, c = await a.read(); | ||
yield { text }; | ||
chunk = await reader.read(); | ||
} | ||
} | ||
function y(e) { | ||
let r = ""; | ||
const o = async (t) => { | ||
if (r || (r = t.env.ACCESS_KEY), !r) | ||
function poe(options) { | ||
function handleQuery(query, c) { | ||
return streamSSE(c, async (stream) => { | ||
await stream.writeSSE({ | ||
event: "meta", | ||
data: JSON.stringify({ content_type: "text/markdown" }) | ||
}); | ||
const list = options.getResponse(query); | ||
for await (const it of list) { | ||
if (it.is_replace_response) { | ||
stream.writeSSE({ | ||
event: "replace_response", | ||
data: JSON.stringify(it) | ||
}); | ||
} else { | ||
stream.writeSSE({ event: "text", data: JSON.stringify(it) }); | ||
} | ||
} | ||
stream.writeSSE({ event: "done", data: JSON.stringify({}) }); | ||
}); | ||
} | ||
let key = ""; | ||
const handler = async (c) => { | ||
if (!key) { | ||
throw new Error("Access Key is required"); | ||
function n(c) { | ||
return f(t, async (i) => { | ||
await i.writeSSE({ | ||
event: "meta", | ||
data: JSON.stringify({ content_type: "text/markdown" }) | ||
}); | ||
const d = e.getResponse(c); | ||
for await (const u of d) | ||
u.is_replace_response ? i.writeSSE({ | ||
event: "replace_response", | ||
data: JSON.stringify(u) | ||
}) : i.writeSSE({ event: "text", data: JSON.stringify(u) }); | ||
i.writeSSE({ event: "done", data: JSON.stringify({}) }); | ||
}); | ||
} | ||
if (t.req.header().authorization !== `Bearer ${r}`) | ||
return t.text("Unauthorized", 401); | ||
const a = await t.req.json(); | ||
switch (a.type) { | ||
const authHeader = c.req.header().authorization; | ||
if (authHeader !== `Bearer ${key}`) { | ||
return c.text("Unauthorized", 401); | ||
} | ||
const req = await c.req.json(); | ||
switch (req.type) { | ||
case "query": | ||
return n(a); | ||
return handleQuery(req, c); | ||
case "settings": | ||
return t.json(await e.getSettings(a)); | ||
return c.json(await options.getSettings(req)); | ||
case "report_feedback": | ||
return await e.onFeedback?.(a), t.json({}); | ||
await options.onFeedback?.(req); | ||
return c.json({}); | ||
case "report_error": | ||
return await e.onError?.(a), t.json({}); | ||
await options.onError?.(req); | ||
return c.json({}); | ||
default: | ||
return t.text("501 Not Implemented", 501); | ||
return c.text("501 Not Implemented", 501); | ||
} | ||
}; | ||
if (!e.name) | ||
if (!options.name) { | ||
throw new Error("name and key are required"); | ||
} | ||
return { | ||
handler: o, | ||
set accessKey(t) { | ||
r = t; | ||
handler, | ||
set accessKey(value) { | ||
key = value; | ||
}, | ||
syncBotSettings: () => p(e.name, r), | ||
streamRequest: (t, n) => m(t, n, r) | ||
syncBotSettings: () => syncBotSettings(options.name, key), | ||
streamRequest: (request, botName) => streamRequest(request, botName, key) | ||
}; | ||
} | ||
export { | ||
y as poe | ||
}; | ||
export { poe }; | ||
//# sourceMappingURL=index.js.map |
@@ -35,4 +35,4 @@ import { Handler } from 'hono'; | ||
export declare function parseSSEMessage(message: string): ParsedDone | ParsedText; | ||
export declare function streamSSETransformStream(): TransformStream<string, { | ||
event: "text" | "done"; | ||
export declare function sseTransformStream(): TransformStream<string, { | ||
event: "text"; | ||
data: any; | ||
@@ -39,0 +39,0 @@ }>; |
{ | ||
"name": "fastapi-poe", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"type": "module", | ||
@@ -5,0 +5,0 @@ "bin": { |
@@ -10,3 +10,3 @@ { | ||
"dependencies": { | ||
"fastapi-poe": "^0.1.0", | ||
"fastapi-poe": "^0.2.1", | ||
"hono": "^4.5.6" | ||
@@ -13,0 +13,0 @@ }, |
@@ -17,5 +17,2 @@ import { Hono } from 'hono' | ||
}, | ||
allow_attachments: false, | ||
suggested_replies: true, | ||
enable_markdown: true, | ||
} | ||
@@ -22,0 +19,0 @@ }, |
@@ -29,21 +29,21 @@ # fastapi-poe | ||
```ts | ||
import { Hono } from 'hono' | ||
import { poe } from 'fastapi-poe' | ||
const app = new Hono() | ||
interface Env { | ||
ACCESS_KEY: string | ||
} | ||
const bot = poe({ | ||
name: 'custom-bot', | ||
// settings, https://creator.poe.com/docs/poe-protocol-specification#settings | ||
name: 'poe-bot-template', | ||
getSettings() { | ||
return { | ||
// declare other bots as dependencies | ||
server_bot_dependencies: { | ||
'Claude-3.5-Sonnet': 1, | ||
}, | ||
allow_attachments: false, | ||
suggested_replies: true, | ||
enable_markdown: true, | ||
} | ||
}, | ||
// get response from bot, https://creator.poe.com/docs/poe-protocol-specification#query | ||
async *getResponse(req) { | ||
// call other bots | ||
for await (const response of bot.streamRequest(req, 'Claude-3.5-Sonnet')) { | ||
@@ -56,2 +56,12 @@ yield { | ||
}) | ||
app.post('/', bot.handler) | ||
app.get('/', async (c) => c.text('Hello Hono!')) | ||
export default { | ||
async fetch(request: Request, env: Env, ctx: ExecutionContext) { | ||
bot.accessKey = env.ACCESS_KEY | ||
return app.fetch(request, env, ctx) | ||
}, | ||
} | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
40491
30
429
66