create-loopengine 0.1.58 → 0.1.60
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/README.md +1 -1
- package/dist/cli.js +1 -1
- package/package.json +1 -1
- package/template/.env.example +5 -4
- package/template/README.md +1 -1
- package/template/adapters/http.ts +52 -1
- package/template/agents/example-agent/index.ts +7 -8
- package/template/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ clone of loopengine's own source repo.
|
|
|
16
16
|
npx create-loopengine@latest my-agents
|
|
17
17
|
cd my-agents
|
|
18
18
|
npm install
|
|
19
|
-
cp .env.example .env # fill in
|
|
19
|
+
cp .env.example .env # fill in OPENAI_API_KEY (or set it later on the Models page)
|
|
20
20
|
npm run dev # HTTP server on :8787
|
|
21
21
|
```
|
|
22
22
|
|
package/dist/cli.js
CHANGED
|
@@ -285,7 +285,7 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
285
285
|
console.log();
|
|
286
286
|
console.log(` cd ${destination}`);
|
|
287
287
|
console.log(' npm install');
|
|
288
|
-
console.log(' cp .env.example .env # fill in
|
|
288
|
+
console.log(' cp .env.example .env # fill in OPENAI_API_KEY (or set it on the Models page)');
|
|
289
289
|
console.log(' npm run dev');
|
|
290
290
|
}
|
|
291
291
|
catch (err) {
|
package/package.json
CHANGED
package/template/.env.example
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
3
|
-
|
|
4
|
-
#
|
|
1
|
+
# The example agent uses OpenAI (agents/example-agent/index.ts's `model`).
|
|
2
|
+
# Set the key here, or on the console's Models page once the server runs.
|
|
3
|
+
OPENAI_API_KEY=
|
|
4
|
+
# Switching the agent to another provider? Set its key instead, e.g.:
|
|
5
|
+
# ANTHROPIC_API_KEY=
|
|
5
6
|
|
|
6
7
|
PORT=8787
|
|
7
8
|
|
package/template/README.md
CHANGED
|
@@ -153,6 +153,10 @@ import {
|
|
|
153
153
|
searchPublicAbilities,
|
|
154
154
|
fetchLatestAbilityVersion,
|
|
155
155
|
describeModelProviders,
|
|
156
|
+
loopengineVersion,
|
|
157
|
+
setModelProviderKey,
|
|
158
|
+
clearModelProviderKey,
|
|
159
|
+
ModelKeyError,
|
|
156
160
|
describeGateways,
|
|
157
161
|
scaffoldAgent,
|
|
158
162
|
AgentNameError,
|
|
@@ -2981,8 +2985,55 @@ const server = createServer(async (req, res) => {
|
|
|
2981
2985
|
res.writeHead(200, { 'content-type': 'text/html' }).end(globalConfigPageHtml)
|
|
2982
2986
|
return
|
|
2983
2987
|
}
|
|
2988
|
+
// What the sidebar's sign-in line and the Settings section show. In
|
|
2989
|
+
// hosted mode the email and dashboard link come from the router's
|
|
2990
|
+
// signed identity, so they can't be set by the caller.
|
|
2991
|
+
if (req.method === 'GET' && pathname === '/config/settings') {
|
|
2992
|
+
const identity = trustedProxySecret ? verifyProxyIdentity(req.headers[TRUSTED_PROXY_IDENTITY_HEADER], trustedProxySecret) : undefined
|
|
2993
|
+
const signIn = identity
|
|
2994
|
+
? {
|
|
2995
|
+
mode: 'hosted',
|
|
2996
|
+
email: identity.email ?? null,
|
|
2997
|
+
dashboardUrl: identity.dashboardUrl && /^https?:\/\//.test(identity.dashboardUrl) ? identity.dashboardUrl : null,
|
|
2998
|
+
}
|
|
2999
|
+
: { mode: adminAuth ? 'basic' : 'open' }
|
|
3000
|
+
res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' }).end(
|
|
3001
|
+
JSON.stringify({
|
|
3002
|
+
signIn,
|
|
3003
|
+
version: loopengineVersion(),
|
|
3004
|
+
environment: process.env.LOOPENGINE_ENV ?? 'production',
|
|
3005
|
+
publicUrl: process.env.LOOPENGINE_PUBLIC_URL ?? null,
|
|
3006
|
+
scheduler: process.env.LOOPENGINE_SCHEDULER === 'off' ? 'off' : 'on',
|
|
3007
|
+
}),
|
|
3008
|
+
)
|
|
3009
|
+
return
|
|
3010
|
+
}
|
|
2984
3011
|
if (req.method === 'GET' && pathname === '/config/models') {
|
|
2985
|
-
res.writeHead(200, { 'content-type': 'application/json' }).end(
|
|
3012
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(
|
|
3013
|
+
JSON.stringify({ ...describeModelProviders(listAgents(), (name) => getEntry(name)?.config), canSave: secretsProtected }),
|
|
3014
|
+
)
|
|
3015
|
+
return
|
|
3016
|
+
}
|
|
3017
|
+
// A provider's API key, from the Models page: write-only, never read
|
|
3018
|
+
// back. Same rule as the Environment tab: refused on a server with no
|
|
3019
|
+
// auth, since anyone who could reach it could replace the key.
|
|
3020
|
+
const modelKeyMatch = (req.method === 'PUT' || req.method === 'DELETE') && pathname.match(/^\/config\/models\/([a-z]+)\/key$/)
|
|
3021
|
+
if (modelKeyMatch) {
|
|
3022
|
+
if (!secretsProtected) {
|
|
3023
|
+
res.writeHead(403, { 'content-type': 'application/json' }).end(
|
|
3024
|
+
JSON.stringify({ error: 'Refusing to store an API key without LOOPENGINE_ADMIN_AUTH configured: set it first, or put the key in .env.' }),
|
|
3025
|
+
)
|
|
3026
|
+
return
|
|
3027
|
+
}
|
|
3028
|
+
try {
|
|
3029
|
+
if (req.method === 'PUT') await setModelProviderKey(modelKeyMatch[1], (await readJsonBody(req)).key)
|
|
3030
|
+
else await clearModelProviderKey(modelKeyMatch[1])
|
|
3031
|
+
} catch (err) {
|
|
3032
|
+
const status = err instanceof ModelKeyError ? 400 : 500
|
|
3033
|
+
res.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }))
|
|
3034
|
+
return
|
|
3035
|
+
}
|
|
3036
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true }))
|
|
2986
3037
|
return
|
|
2987
3038
|
}
|
|
2988
3039
|
if (req.method === 'GET' && pathname === '/config/gateways') {
|
|
@@ -12,18 +12,17 @@
|
|
|
12
12
|
// resolves relative to loopengine's own installed location, not your
|
|
13
13
|
// project, so it only works inside loopengine's own repo — see
|
|
14
14
|
// tools/index.ts's own comment.
|
|
15
|
-
import {
|
|
15
|
+
import type { AgentConfig } from 'loopengine'
|
|
16
16
|
import { tools } from './tools/index.js'
|
|
17
17
|
|
|
18
18
|
export const config: AgentConfig = {
|
|
19
19
|
name: 'example-agent',
|
|
20
20
|
systemPrompt: 'You are a helpful assistant.',
|
|
21
21
|
tools,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
return createAnthropicModelCall({ model: 'claude-sonnet-5' })
|
|
22
|
+
// Reads OPENAI_API_KEY: set it on the console's Models page, or in .env.
|
|
23
|
+
// For another provider, change `provider` and `model` (e.g.
|
|
24
|
+
// { provider: 'anthropic', model: 'claude-sonnet-5' } with
|
|
25
|
+
// ANTHROPIC_API_KEY). reasoningEffort 'none' lets this gpt-5.x model use
|
|
26
|
+
// function tools; see AgentModelConfig in loopengine's agent-config.
|
|
27
|
+
model: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'none' },
|
|
29
28
|
}
|