create-loopengine 0.1.39 → 0.1.42
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/package.json +2 -2
- package/template/adapters/http.ts +73 -4
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-loopengine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"description": "Scaffold a new loopengine agent project — a runnable HTTP server with one example agent, in your own repo.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://loopengine.co",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/loopengine-co/create-loopengine.git"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
13
|
"create-loopengine": "dist/cli.js"
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
systemTools,
|
|
65
65
|
systemSkillsDir,
|
|
66
66
|
type AgentConfig,
|
|
67
|
+
type AgentModelConfig,
|
|
67
68
|
type ToolDefinition,
|
|
68
69
|
type ModelContentBlock,
|
|
69
70
|
type RunAgentResult,
|
|
@@ -356,7 +357,16 @@ async function describeAgent(entry: RegistryEntry): Promise<Record<string, unkno
|
|
|
356
357
|
name: config.name,
|
|
357
358
|
systemPrompt: config.systemPrompt,
|
|
358
359
|
model: config.model
|
|
359
|
-
? {
|
|
360
|
+
? {
|
|
361
|
+
provider: config.model.provider,
|
|
362
|
+
model: config.model.model,
|
|
363
|
+
maxTokens: config.model.maxTokens,
|
|
364
|
+
// Only the 'openai' variant of AgentModelConfig carries this —
|
|
365
|
+
// an `in` check rather than a cast, so a provider that can
|
|
366
|
+
// never have it just reports undefined instead of needing its
|
|
367
|
+
// own narrowing branch here.
|
|
368
|
+
reasoningEffort: 'reasoningEffort' in config.model ? config.model.reasoningEffort : undefined,
|
|
369
|
+
}
|
|
360
370
|
: 'custom (module exports its own createModelCall)',
|
|
361
371
|
maxTurns: config.maxTurns ?? 25,
|
|
362
372
|
contextBudgetTokens: config.contextBudgetTokens ?? 100000,
|
|
@@ -830,6 +840,33 @@ async function handleCreateAgent(req: IncomingMessage, res: ServerResponse): Pro
|
|
|
830
840
|
// why that's regenerable on its own, unlike the rest of an already-
|
|
831
841
|
// imported module). No restart needed, same as every other admin edit
|
|
832
842
|
// in this app.
|
|
843
|
+
// model.maxTokens/model.reasoningEffort are edit-only — scaffoldAgent has
|
|
844
|
+
// no use for either at creation time, so they're parsed here rather than
|
|
845
|
+
// widening parseAgentTemplateOptions/AgentTemplateOptions (shared with
|
|
846
|
+
// agent creation) for something only this route needs. Only ever called
|
|
847
|
+
// after parseAgentTemplateOptions already validated provider/model, so
|
|
848
|
+
// there's nothing to re-validate here beyond these two fields themselves.
|
|
849
|
+
function parseEditModelExtras(
|
|
850
|
+
body: Record<string, unknown>,
|
|
851
|
+
model: AgentEditableFields['model'],
|
|
852
|
+
): { ok: true; value: AgentEditableFields['model'] } | { ok: false; error: string } {
|
|
853
|
+
if (!model || typeof body.model !== 'object' || body.model === null) return { ok: true, value: model }
|
|
854
|
+
const rawModel = body.model as Record<string, unknown>
|
|
855
|
+
if (rawModel.maxTokens !== undefined && rawModel.maxTokens !== null) {
|
|
856
|
+
if (typeof rawModel.maxTokens !== 'number' || !Number.isInteger(rawModel.maxTokens) || rawModel.maxTokens < 1) {
|
|
857
|
+
return { ok: false, error: 'model.maxTokens must be a positive integer' }
|
|
858
|
+
}
|
|
859
|
+
model.maxTokens = rawModel.maxTokens
|
|
860
|
+
}
|
|
861
|
+
if (rawModel.reasoningEffort !== undefined && rawModel.reasoningEffort !== null) {
|
|
862
|
+
if (typeof rawModel.reasoningEffort !== 'string') {
|
|
863
|
+
return { ok: false, error: 'model.reasoningEffort must be a string' }
|
|
864
|
+
}
|
|
865
|
+
model.reasoningEffort = rawModel.reasoningEffort
|
|
866
|
+
}
|
|
867
|
+
return { ok: true, value: model }
|
|
868
|
+
}
|
|
869
|
+
|
|
833
870
|
async function handleEditAgent(req: IncomingMessage, res: ServerResponse, agentName: string): Promise<void> {
|
|
834
871
|
if (!getEntry(agentName)) {
|
|
835
872
|
res.writeHead(404, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `unknown agent '${agentName}'` }))
|
|
@@ -841,12 +878,17 @@ async function handleEditAgent(req: IncomingMessage, res: ServerResponse, agentN
|
|
|
841
878
|
res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: parsedOptions.error }))
|
|
842
879
|
return
|
|
843
880
|
}
|
|
881
|
+
const parsedModelExtras = parseEditModelExtras(body, parsedOptions.value.model)
|
|
882
|
+
if (!parsedModelExtras.ok) {
|
|
883
|
+
res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: parsedModelExtras.error }))
|
|
884
|
+
return
|
|
885
|
+
}
|
|
844
886
|
const parsedLimits = parseAgentLimitsFields(body)
|
|
845
887
|
if (!parsedLimits.ok) {
|
|
846
888
|
res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: parsedLimits.error }))
|
|
847
889
|
return
|
|
848
890
|
}
|
|
849
|
-
const editFields: AgentEditableFields = { ...parsedOptions.value, ...parsedLimits.value }
|
|
891
|
+
const editFields: AgentEditableFields = { ...parsedOptions.value, model: parsedModelExtras.value, ...parsedLimits.value }
|
|
850
892
|
if (Object.keys(editFields).length === 0) {
|
|
851
893
|
res
|
|
852
894
|
.writeHead(400, { 'content-type': 'application/json' })
|
|
@@ -871,8 +913,27 @@ async function handleEditAgent(req: IncomingMessage, res: ServerResponse, agentN
|
|
|
871
913
|
if (result.skillIndexBudgetTokens !== undefined) configPatch.skillIndexBudgetTokens = result.skillIndexBudgetTokens
|
|
872
914
|
let createModelCall: RegistryEntry['createModelCall'] | undefined
|
|
873
915
|
if (result.model) {
|
|
874
|
-
|
|
875
|
-
|
|
916
|
+
// editAgentFile's own AgentEditResult.model is one flat shape across
|
|
917
|
+
// every provider (see that file's own reasoningEffort-only-for-openai
|
|
918
|
+
// check) — AgentModelConfig is a discriminated union instead, so
|
|
919
|
+
// reasoningEffort only ever type-checks on the 'openai' branch.
|
|
920
|
+
// reasoningEffort itself is narrowed no further than `string` at this
|
|
921
|
+
// boundary (same as `model` — an arbitrary provider-defined name, not
|
|
922
|
+
// an enum this layer validates); an invalid value surfaces as a clear
|
|
923
|
+
// rejection from the provider's own API, same as an invalid model name
|
|
924
|
+
// already does.
|
|
925
|
+
const m = result.model
|
|
926
|
+
const modelConfig: AgentModelConfig =
|
|
927
|
+
m.provider === 'openai'
|
|
928
|
+
? {
|
|
929
|
+
provider: 'openai',
|
|
930
|
+
model: m.model,
|
|
931
|
+
maxTokens: m.maxTokens,
|
|
932
|
+
reasoningEffort: m.reasoningEffort as Extract<AgentModelConfig, { provider: 'openai' }>['reasoningEffort'],
|
|
933
|
+
}
|
|
934
|
+
: { provider: m.provider, model: m.model, maxTokens: m.maxTokens }
|
|
935
|
+
configPatch.model = modelConfig
|
|
936
|
+
createModelCall = await synthesizeCreateModelCall(modelConfig)
|
|
876
937
|
}
|
|
877
938
|
updateAgent(agentName, { config: configPatch, createModelCall })
|
|
878
939
|
|
|
@@ -2431,6 +2492,14 @@ const server = createServer(async (req, res) => {
|
|
|
2431
2492
|
|
|
2432
2493
|
await handleMessages(req, res, decodeURIComponent(match[1]))
|
|
2433
2494
|
} catch (err) {
|
|
2495
|
+
// headersSent means some route already called writeHead (200,
|
|
2496
|
+
// usually) before an awaited call after it threw — the exact
|
|
2497
|
+
// "Unexpected end of JSON input" symptom on the client, since
|
|
2498
|
+
// res.end() here with no argument sends an empty body under
|
|
2499
|
+
// already-committed headers. Logged, not just silently ended, so
|
|
2500
|
+
// that symptom is actually diagnosable from the server side instead
|
|
2501
|
+
// of a client-side error with no server-side trace at all.
|
|
2502
|
+
console.error('[loopengine] unhandled error in request handler:', err)
|
|
2434
2503
|
if (res.headersSent) {
|
|
2435
2504
|
res.end()
|
|
2436
2505
|
} else {
|