create-loopengine 0.1.44 → 0.1.45

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-loopengine",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
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",
@@ -137,6 +137,7 @@ import {
137
137
  import { SkillGarden } from 'loopengine'
138
138
  import type { Decision, PendingApproval } from 'actauth'
139
139
  import type { LoopEvent } from 'loopengine'
140
+ import { storageSigners } from 'loopengine'
140
141
 
141
142
  const sessions = createSessionStore()
142
143
  const checkpoints = createCheckpointStore()
@@ -2053,90 +2054,58 @@ async function respondAfterResolution(res: ServerResponse, finalCheckpoint: Turn
2053
2054
  // long as the click that requested it takes to resolve, not however
2054
2055
  // long a chat reply might sit around unopened).
2055
2056
  //
2056
- // Deliberately generic, not tied to any one ability — any ability using
2057
- // GCS storage can point a tool's own returned URL here instead of
2058
- // signing itself, as long as it authenticates the same way
2057
+ // Deliberately generic on two axes, not tied to any one ability or
2058
+ // cloud provider: any ability can point a tool's own returned URL here
2059
+ // instead of signing itself, as long as it authenticates the same way
2060
+ // loopengine's own storageSigners.gcs already does
2061
+ // (GOOGLE_APPLICATION_CREDENTIALS_JSON, falling back to Application
2062
+ // Default Credentials) — that's the same logic
2059
2063
  // lp-product-ad-images'/lp-file-archiver's own buildGcsStorageClient
2060
- // already do (GOOGLE_APPLICATION_CREDENTIALS_JSON, falling back to
2061
- // Application Default Credentials) — this is that same logic,
2062
- // centralized, rather than every GCS-backed ability duplicating it
2063
- // (which they already do today for the *upload* side; this just does
2064
- // the same for signing).
2064
+ // already duplicate for the *upload* side, centralized in loopengine
2065
+ // core for signing instead. `provider` picks which one of
2066
+ // loopengine's own storageSigners actually does the signing (only
2067
+ // `gcs` exists today) — adding S3/Azure/etc. later is a loopengine
2068
+ // core change, not something this scaffolded file needs to know about.
2065
2069
  //
2066
2070
  // Sits behind the same isAuthorized() Basic Auth gate every other route
2067
2071
  // on this server already requires (checked once, unconditionally,
2068
2072
  // before any routing below even runs) — not a separately public
2069
2073
  // endpoint. A browser that already loaded /playground has that same
2070
2074
  // origin's Basic Auth credentials cached, so a plain
2071
- // <img src="/gcs-redirect?..."> or download link both just work with no
2072
- // extra wiring; anything without those credentials gets the same 401
2073
- // every other route already gives it.
2074
- async function handleGcsRedirect(req: IncomingMessage, res: ServerResponse): Promise<void> {
2075
+ // <img src="/storage-redirect?..."> or download link both just work
2076
+ // with no extra wiring; anything without those credentials gets the
2077
+ // same 401 every other route already gives it.
2078
+ async function handleStorageRedirect(req: IncomingMessage, res: ServerResponse): Promise<void> {
2075
2079
  const url = new URL(req.url ?? '/', 'http://localhost')
2080
+ const provider = url.searchParams.get('provider')
2076
2081
  const bucket = url.searchParams.get('bucket')
2077
2082
  const object = url.searchParams.get('object')
2078
2083
  const disposition = url.searchParams.get('disposition')
2079
2084
  const filename = url.searchParams.get('filename')
2080
2085
 
2081
- if (!bucket || !object) {
2082
- res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'gcs-redirect requires both bucket and object query params' }))
2086
+ if (!provider || !bucket || !object) {
2087
+ res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'storage-redirect requires provider, bucket, and object query params' }))
2083
2088
  return
2084
2089
  }
2085
2090
  if (disposition && disposition !== 'inline' && disposition !== 'attachment') {
2086
2091
  res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: `disposition must be "inline" or "attachment" — got "${disposition}"` }))
2087
2092
  return
2088
2093
  }
2089
-
2090
- // Imported by a variable, not a string literal, so tsc treats this as
2091
- // Promise<any> instead of trying to resolve @google-cloud/storage's
2092
- // own types at compile time — same reasoning
2093
- // lp-product-ad-images'/lp-file-archiver's own saveImage/saveArchive
2094
- // already use, for the same reason: installing it is only required at
2095
- // runtime for a deployment that actually uses this route, not every
2096
- // scaffolded project.
2097
- const gcsModuleName = '@google-cloud/storage'
2098
- let gcs: any
2099
- try {
2100
- gcs = await import(gcsModuleName)
2101
- } catch {
2102
- res.writeHead(500, { 'content-type': 'application/json' }).end(
2103
- JSON.stringify({ error: 'gcs-redirect requires the @google-cloud/storage package — npm install @google-cloud/storage in your own project.' }),
2094
+ const signer = storageSigners[provider]
2095
+ if (!signer) {
2096
+ res.writeHead(400, { 'content-type': 'application/json' }).end(
2097
+ JSON.stringify({ error: `storage-redirect: unknown provider "${provider}" — supported: ${Object.keys(storageSigners).join(', ')}` }),
2104
2098
  )
2105
2099
  return
2106
2100
  }
2107
2101
 
2108
- let client: any
2109
- const credentialsJson = process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON
2110
- if (credentialsJson) {
2111
- let credentials: { project_id?: string }
2112
- try {
2113
- credentials = JSON.parse(credentialsJson)
2114
- } catch {
2115
- res.writeHead(500, { 'content-type': 'application/json' }).end(
2116
- JSON.stringify({
2117
- error: 'GOOGLE_APPLICATION_CREDENTIALS_JSON is not valid JSON — paste the entire contents of the downloaded service-account key file, unedited.',
2118
- }),
2119
- )
2120
- return
2121
- }
2122
- client = new gcs.Storage({ credentials, projectId: credentials.project_id })
2123
- } else {
2124
- client = new gcs.Storage()
2125
- }
2126
-
2127
2102
  try {
2128
- const file = client.bucket(bucket).file(object)
2129
- const expires = Date.now() + 5 * 60 * 1000
2130
- const signOptions: Record<string, unknown> = { action: 'read', expires }
2131
- if (disposition === 'attachment') {
2132
- signOptions.responseDisposition = `attachment; filename="${filename || object.split('/').pop()}"`
2133
- }
2134
- const [signedUrl] = await file.getSignedUrl(signOptions)
2103
+ const signedUrl = await signer({ bucket, object, disposition: (disposition as 'inline' | 'attachment' | null) ?? undefined, filename: filename ?? undefined })
2135
2104
  res.writeHead(302, { location: signedUrl }).end()
2136
2105
  } catch (err) {
2137
2106
  res
2138
2107
  .writeHead(502, { 'content-type': 'application/json' })
2139
- .end(JSON.stringify({ error: `gcs-redirect: could not sign a URL for gs://${bucket}/${object}: ${err instanceof Error ? err.message : String(err)}` }))
2108
+ .end(JSON.stringify({ error: `storage-redirect: could not sign a URL for ${provider}://${bucket}/${object}: ${err instanceof Error ? err.message : String(err)}` }))
2140
2109
  }
2141
2110
  }
2142
2111
 
@@ -2586,8 +2555,8 @@ const server = createServer(async (req, res) => {
2586
2555
  return
2587
2556
  }
2588
2557
 
2589
- if (req.method === 'GET' && pathname === '/gcs-redirect') {
2590
- await handleGcsRedirect(req, res)
2558
+ if (req.method === 'GET' && pathname === '/storage-redirect') {
2559
+ await handleStorageRedirect(req, res)
2591
2560
  return
2592
2561
  }
2593
2562