Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ curl -s -X POST https://axionlabsai-lumen.hf.space/gradio_api/v1/chat/completion
# → should return JSON with choices, not 405 HTML

# Also test via CF Worker (full stack):
curl -s -X POST https://api.amplifiedsmp.org/v1/chat/completions \
curl -s -X POST https://api.sennoric.com/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"lumen","messages":[{"role":"user","content":"hi"}]}'
```
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The deployed public website is maintained separately in
### Install (recommended)

```bash
curl -fsSL https://axion.amplifiedsmp.org/install.sh | sh
curl -fsSL https://sennoric.com/install.sh | sh
```

Or directly via npm:
Expand Down
13 changes: 13 additions & 0 deletions api-proxy-cf/migrations/041_domain_migration_codes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- Short-lived, one-time browser handoffs used only while moving authenticated
-- sessions from amplifiedsmp.org to sennoric.com.
CREATE TABLE domain_migration_codes (
code TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
redeemed_at INTEGER,
FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE INDEX idx_domain_migration_codes_expires
ON domain_migration_codes(expires_at);
Comment thread
Ravikxx marked this conversation as resolved.
6 changes: 3 additions & 3 deletions api-proxy-cf/src/chatGeneration.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ALLOWED_WEB_ORIGINS } from './webOrigins.js'

const COMPLETIONS_URL = 'https://api.amplifiedsmp.org/v1/chat/completions'
const COMPLETIONS_URL = 'https://api.sennoric.com/v1/chat/completions'

// Partial text is written to storage at most this often. Frequent enough that a
// reader attaching after an eviction sees almost everything, rare enough that a
Expand Down Expand Up @@ -30,7 +30,7 @@ async function sendEmail(resendKey, { to, subject, html }) {
const res = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${resendKey}` },
body: JSON.stringify({ from: 'Sennoric <noreply@amplifiedsmp.org>', to: [to], subject, html }),
body: JSON.stringify({ from: 'Sennoric <noreply@sennoric.com>', to: [to], subject, html }),
})
if (!res.ok) {
const body = await res.text().catch(() => '')
Expand Down Expand Up @@ -63,7 +63,7 @@ async function notifyScheduledCompletion(env, job, { status, error }) {
html: `<div style="font-family:system-ui,sans-serif;max-width:480px;margin:0 auto;padding:32px;background:#0f0f11;color:#e8e8f0">
<h2 style="margin:0 0 8px;color:#e8e8f0">"${name}" ${ok ? 'finished' : 'failed'}</h2>
${body}
<a href="https://axion.amplifiedsmp.org/chat" style="display:inline-block;background:#e8602c;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;font-weight:700">Open Sennoric &rarr;</a>
<a href="https://sennoric.com/chat" style="display:inline-block;background:#e8602c;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;font-weight:700">Open Sennoric &rarr;</a>
</div>`,
})
} catch (err) {
Expand Down
232 changes: 163 additions & 69 deletions api-proxy-cf/src/index.js

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions api-proxy-cf/src/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function labelFor(service) {
// the request would normally go through, with no network hop at all.
async function checkSennoricApi(env, appFetch) {
try {
const res = await appFetch(new Request('https://api.amplifiedsmp.org/v1/models'))
const res = await appFetch(new Request('https://api.sennoric.com/v1/models'))
return res.ok
? { service: 'axion_api', status: 'up', detail: '' }
: { service: 'axion_api', status: 'down', detail: `HTTP ${res.status}` }
Expand All @@ -45,7 +45,7 @@ async function checkLumen(env, fetchImpl) {

async function checkWebsite(env, fetchImpl) {
try {
const res = await fetchImpl('https://axion.amplifiedsmp.org/', { method: 'GET' })
const res = await fetchImpl('https://sennoric.com/', { method: 'GET' })
return res.ok
? { service: 'website', status: 'up', detail: '' }
: { service: 'website', status: 'down', detail: `HTTP ${res.status}` }
Expand Down Expand Up @@ -74,7 +74,7 @@ async function alertAdmin(env, { subject, html }) {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${env.RESEND_API_KEY}` },
body: JSON.stringify({
from: 'Sennoric Status <status@amplifiedsmp.org>',
from: 'Sennoric Status <status@sennoric.com>',
to: [env.ADMIN_ALERT_EMAIL],
subject,
html,
Expand Down Expand Up @@ -108,7 +108,7 @@ async function evaluateIncident(env, result, nowIso) {
'INSERT INTO status_incident_updates (id, incident_id, status, body, created_at) VALUES (?,?,?,?,?)'
).bind(crypto.randomUUID(), id, 'investigating', body, nowIso),
])
const editLink = `https://axion.amplifiedsmp.org/admin#incident-${id}`
const editLink = `https://sennoric.com/admin#incident-${id}`
await alertAdmin(env, {
subject: `[Sennoric Status] ${title}`,
html: emailWrap(`
Expand All @@ -131,7 +131,7 @@ async function evaluateIncident(env, result, nowIso) {
'INSERT INTO status_incident_updates (id, incident_id, status, body, created_at) VALUES (?,?,?,?,?)'
).bind(crypto.randomUUID(), existing.id, 'resolved', body, nowIso),
])
const editLink = `https://axion.amplifiedsmp.org/admin#incident-${existing.id}`
const editLink = `https://sennoric.com/admin#incident-${existing.id}`
await alertAdmin(env, {
subject: `[Sennoric Status] Resolved: ${existing.title}`,
html: emailWrap(`
Expand Down
9 changes: 3 additions & 6 deletions api-proxy-cf/src/webOrigins.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
// chatGeneration.js can both import it without a circular dependency
// (index.js is what imports ChatGeneration from chatGeneration.js).
//
// NEW_WEB_ORIGIN (sennoric.com) is not live yet as of this change --
// DNS/GitHub Pages cutover pending. Accepted here ahead of time so CORS
// doesn't need another deploy the moment it goes live.
export const WEB_ORIGIN = 'https://axion.amplifiedsmp.org'
export const NEW_WEB_ORIGIN = 'https://sennoric.com'
export const ALLOWED_WEB_ORIGINS = [WEB_ORIGIN, NEW_WEB_ORIGIN]
export const WEB_ORIGIN = 'https://sennoric.com'
export const LEGACY_WEB_ORIGIN = 'https://axion.amplifiedsmp.org'
export const ALLOWED_WEB_ORIGINS = [WEB_ORIGIN, LEGACY_WEB_ORIGIN]
4 changes: 2 additions & 2 deletions api-proxy-cf/test/billing.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ test('only an authenticated admin can manually run pending message review', asyn
assert.match(runResult.run_id, /^[0-9a-f-]{36}$/)
assert.equal(
runResult.details_url,
`https://axion.amplifiedsmp.org/admin-moderation?run=${runResult.run_id}`,
`https://sennoric.com/admin-moderation?run=${runResult.run_id}`,
)
const message = db.prepare(
'SELECT review_status, review_run_id FROM message_log WHERE id=1'
Expand Down Expand Up @@ -1322,7 +1322,7 @@ test('chat completions require an account and never call the model for anonymous
const body = await response.json()
assert.equal(body.error.type, 'authentication_error')
assert.equal(body.error.signup_required, true)
assert.equal(body.error.signup_url, 'https://axion.amplifiedsmp.org/chat')
assert.equal(body.error.signup_url, 'https://sennoric.com/chat')
assert.equal(upstreamCalls, 0)
assert.equal(db.prepare('SELECT COUNT(*) AS count FROM message_log').first().count, 0)
assert.equal(db.prepare("SELECT COUNT(*) AS count FROM rate_limits WHERE key LIKE 'free:%'").first().count, 0)
Expand Down
2 changes: 1 addition & 1 deletion api-proxy-cf/test/chat-generation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ test('the Durable Object appends the assistant reply and completes the job after
globalThis.fetch = realFetch
}

assert.equal(requestSeen.url, 'https://api.amplifiedsmp.org/v1/chat/completions')
assert.equal(requestSeen.url, 'https://api.sennoric.com/v1/chat/completions')
assert.equal(requestSeen.options.headers.Authorization, 'Bearer signed-job-token')
// The object owns the only model call, and it streams so watching tabs get
// tokens as they arrive rather than one block at the end.
Expand Down
62 changes: 62 additions & 0 deletions api-proxy-cf/test/desktop-auth.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ class D1TestDatabase {
expires_at INTEGER NOT NULL,
redeemed_at INTEGER
);
CREATE TABLE domain_migration_codes (
code TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
redeemed_at INTEGER
);
Comment thread
Ravikxx marked this conversation as resolved.
`)
}
prepare(sql) { return new Statement(this.database, sql) }
Expand Down Expand Up @@ -221,3 +228,58 @@ test('a password reset invalidates a token minted before it', async () => {
const response = await approve(env, minted, { code_challenge: (await pkcePair()).challenge })
assert.equal(response.status, 401)
})

test('the old HttpOnly session migrates through a signed single-use handoff', async () => {
const { env } = makeEnv()
const token = await sessionToken('u1')
const start = await app.request(
'https://api.amplifiedsmp.org/auth/domain-migrate?return=https%3A%2F%2Faxion.amplifiedsmp.org%2Fkeys%3Ftab%3Dusage',
{ headers: { Cookie: `axion_session=${token}` } },
env,
)

assert.equal(start.status, 302)
const acceptUrl = new URL(start.headers.get('location'))
assert.equal(acceptUrl.origin, 'https://api.sennoric.com')
assert.equal(acceptUrl.pathname, '/auth/domain-migrate/accept')
assert.ok(acceptUrl.searchParams.get('handoff'))
assert.equal(acceptUrl.searchParams.has('token'), false)

const accepted = await app.request(acceptUrl.href, {}, env)
assert.equal(accepted.status, 302)
assert.equal(accepted.headers.get('location'), 'https://sennoric.com/keys?tab=usage')
assert.match(accepted.headers.get('set-cookie'), /Domain=\.sennoric\.com/)
assert.match(accepted.headers.get('set-cookie'), /HttpOnly/)
Comment thread
Ravikxx marked this conversation as resolved.

const replay = await app.request(acceptUrl.href, {}, env)
assert.equal(replay.status, 400)
})

test('domain migration without an old session redirects without minting a handoff', async () => {
const { env } = makeEnv()
const response = await app.request(
'https://api.amplifiedsmp.org/auth/domain-migrate?return=https%3A%2F%2Faxion.amplifiedsmp.org%2Fdocs',
{},
env,
)
assert.equal(response.status, 302)
assert.equal(response.headers.get('location'), 'https://sennoric.com/docs')
})

test('the old website preserves paths and routes account visits through the signed handoff', async () => {
const { env } = makeEnv()

const docs = await app.request('https://axion.amplifiedsmp.org/docs?section=cli', {}, env)
assert.equal(docs.status, 302)
assert.equal(docs.headers.get('location'), 'https://sennoric.com/docs?section=cli')

const keys = await app.request('https://axion.amplifiedsmp.org/keys?tab=usage', {}, env)
assert.equal(keys.status, 302)
const migrate = new URL(keys.headers.get('location'))
assert.equal(migrate.origin, 'https://api.amplifiedsmp.org')
assert.equal(migrate.pathname, '/auth/domain-migrate')
assert.equal(
migrate.searchParams.get('return'),
'https://sennoric.com/keys?tab=usage&domain_migration=checked',
)
})
4 changes: 2 additions & 2 deletions api-proxy-cf/test/desktop-integrations.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ test('GitHub integration OAuth is brokered with PKCE and the provider token is s

mock.method(globalThis, 'fetch', async (input, init) => {
assert.equal(String(input), 'https://github.com/login/oauth/access_token')
assert.equal(JSON.parse(init.body).redirect_uri, 'https://api.amplifiedsmp.org/auth/github/callback')
assert.equal(JSON.parse(init.body).redirect_uri, 'https://api.sennoric.com/auth/github/callback')
return Response.json({ access_token: 'gho_secret', scope: 'repo read:user', token_type: 'bearer' })
})
const callback = await app.request(`/auth/github/callback?code=provider-code&state=${encodeURIComponent(authorizationUrl.searchParams.get('state'))}`, {}, env)
Expand Down Expand Up @@ -151,7 +151,7 @@ test('Notion integration OAuth is brokered without exposing its client secret',
mock.method(globalThis, 'fetch', async (input, init) => {
assert.equal(String(input), 'https://api.notion.com/v1/oauth/token')
assert.equal(init.headers.Authorization, `Basic ${btoa('notion-client:notion-secret')}`)
assert.equal(JSON.parse(init.body).redirect_uri, 'https://api.amplifiedsmp.org/auth/notion/callback')
assert.equal(JSON.parse(init.body).redirect_uri, 'https://api.sennoric.com/auth/notion/callback')
return Response.json({ access_token: 'secret_notion', workspace_name: 'Sennoric' })
})
const callback = await app.request(`/auth/notion/callback?code=provider-code&state=${encodeURIComponent(authorizationUrl.searchParams.get('state'))}`, {}, env)
Expand Down
15 changes: 15 additions & 0 deletions api-proxy-cf/test/migrations.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,18 @@ test('user_settings accepts the same TEXT ids used by users after migration 038'
userId,
)
})

test('domain migration handoffs are single-use and reference users', () => {
const db = new DatabaseSync(':memory:')
db.exec('CREATE TABLE users (id TEXT PRIMARY KEY)')
db.exec(migration('041_domain_migration_codes.sql'))
db.prepare('INSERT INTO users (id) VALUES (?)').run('u1')
db.prepare(`
INSERT INTO domain_migration_codes (code, user_id, created_at, expires_at)
VALUES (?, ?, ?, ?)
`).run('code', 'u1', 1, 2)

const row = db.prepare('SELECT * FROM domain_migration_codes WHERE code=?').get('code')
assert.equal(row.user_id, 'u1')
assert.equal(row.redeemed_at, null)
})
Comment thread
Ravikxx marked this conversation as resolved.
Outdated
2 changes: 1 addition & 1 deletion api-proxy-cf/test/sandbox-route.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ test('anonymous requests (no auth header at all) require signup and are rejected
const body = await response.json()
assert.equal(body.error.type, 'authentication_error')
assert.equal(body.error.signup_required, true)
assert.equal(body.error.signup_url, 'https://axion.amplifiedsmp.org/chat')
assert.equal(body.error.signup_url, 'https://sennoric.com/chat')
})

test('a banned session-token account is rejected (requireAuth already filters banned users to null, same as /v1/chat/completions — so this is a 401, not a 403)', async () => {
Expand Down
2 changes: 1 addition & 1 deletion api-proxy-cf/test/status.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function fetchStub({ axionApiUp = true, lumenUp = true, websiteUp = true } = {})
return async (url) => {
const s = typeof url === 'string' ? url : url.url
if (s.includes('runpod.ai')) return { ok: lumenUp }
if (s.includes('api.amplifiedsmp.org')) return { ok: axionApiUp }
if (s.includes('api.sennoric.com')) return { ok: axionApiUp }
return { ok: websiteUp }
}
}
Expand Down
8 changes: 8 additions & 0 deletions api-proxy-cf/wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,17 @@ new_sqlite_classes = ["BridgeRelay"]
tag = "v2"
new_sqlite_classes = ["ChatGeneration"]

[[routes]]
pattern = "api.sennoric.com/*"
zone_name = "sennoric.com"

[[routes]]
pattern = "api.amplifiedsmp.org/*"
zone_name = "amplifiedsmp.org"

[[routes]]
pattern = "axion.amplifiedsmp.org/*"
zone_name = "amplifiedsmp.org"

[triggers]
crons = ["*/5 * * * *", "0 * * * *", "* * * * *"]
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
{
"cell_type": "markdown",
"metadata": {},
"source": "## After training\n\n- **Serve**: load `lumen-1.3-merged` in vLLM with `--enable-auto-tool-choice --tool-call-parser hermes`\n behind the existing lumen endpoint (`api.amplifiedsmp.org/v1`). Sennoric needs zero changes for text —\n `/model lumen` already points there.\n- **Multimodal serving is extra work, not automatic.** The weights support images/video, but the\n serving stack doesn't yet: vLLM needs multimodal flags, `api-proxy-cf` must accept OpenAI-style\n `image_url` content parts in `/v1/chat/completions`, `chat.html` needs upload UI, and image tokens\n need billing rates. Track as its own task.\n- **Eval**: run a SWE-bench-style held-out set through the deployed model and compare pass-rate\n against base Qwen3.5-9B served the same way. (Eval harness is a separate, not-yet-built piece.)\n- **Regression checks before shipping**:\n - plain chat (a few ordinary questions) — no catastrophic forgetting\n - `<think>` still appears on complex prompts (thinking is ON by default in Qwen3.5)\n - **vision still works** — feed an image and ask for a description; compare against base model.\n This is the check that catches an accidental unfreezing of the vision tower."
"source": "## After training\n\n- **Serve**: load `lumen-1.3-merged` in vLLM with `--enable-auto-tool-choice --tool-call-parser hermes`\n behind the existing lumen endpoint (`api.sennoric.com/v1`). Sennoric needs zero changes for text —\n `/model lumen` already points there.\n- **Multimodal serving is extra work, not automatic.** The weights support images/video, but the\n serving stack doesn't yet: vLLM needs multimodal flags, `api-proxy-cf` must accept OpenAI-style\n `image_url` content parts in `/v1/chat/completions`, `chat.html` needs upload UI, and image tokens\n need billing rates. Track as its own task.\n- **Eval**: run a SWE-bench-style held-out set through the deployed model and compare pass-rate\n against base Qwen3.5-9B served the same way. (Eval harness is a separate, not-yet-built piece.)\n- **Regression checks before shipping**:\n - plain chat (a few ordinary questions) — no catastrophic forgetting\n - `<think>` still appears on complex prompts (thinking is ON by default in Qwen3.5)\n - **vision still works** — feed an image and ask for a description; compare against base model.\n This is the check that catches an accidental unfreezing of the vision tower."
}
],
"metadata": {
Expand Down
4 changes: 2 additions & 2 deletions src/agent/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2054,7 +2054,7 @@ export function classifyProviderError(err, modelAlias) {
const status = err.data.status ?? err?.status ?? err?.response?.status;
if (status === 401) {
if (modelAlias === 'other') return { kind: 'account', message: `Auth failed for custom endpoint. Use /endpoint <url> <model> <key> to set the API key.` };
if (isAxionHostedProvider(resolveProvider(modelAlias))) return { kind: 'account', message: `Invalid or revoked Sennoric credentials. Use /login or /axion-key <your-key> to authenticate.\n→ Sign up or get a key at axion.amplifiedsmp.org/keys` };
if (isAxionHostedProvider(resolveProvider(modelAlias))) return { kind: 'account', message: `Invalid or revoked Sennoric credentials. Use /login or /axion-key <your-key> to authenticate.\n→ Sign up or get a key at sennoric.com/keys` };
return { kind: 'account', message: `Invalid API key for "${modelAlias}". Use /api ${modelAlias} <your-key> to set it.` };
}
if (status === 429) return { kind: 'quota', message: `Rate limited by "${providerLabel}". Wait a moment and try again.` };
Expand All @@ -2081,7 +2081,7 @@ export function classifyProviderError(err, modelAlias) {

if (status === 401 || /unauthorized|invalid.*key|api.?key/i.test(msg)) {
if (modelAlias === 'other') return { kind: 'account', message: `Auth failed for custom endpoint. Use /endpoint <url> <model> <key> to set the API key.` };
if (isAxionHostedProvider(resolveProvider(modelAlias))) return { kind: 'account', message: `Invalid or revoked Sennoric credentials. Use /login or /axion-key <your-key> to authenticate.\n→ Sign up or get a key at axion.amplifiedsmp.org/keys` };
if (isAxionHostedProvider(resolveProvider(modelAlias))) return { kind: 'account', message: `Invalid or revoked Sennoric credentials. Use /login or /axion-key <your-key> to authenticate.\n→ Sign up or get a key at sennoric.com/keys` };
return { kind: 'account', message: `Invalid API key for "${modelAlias}". Use /api ${modelAlias} <your-key> to set it.` };
}
if (status === 429 || /rate.?limit|quota/i.test(msg)) {
Expand Down
2 changes: 1 addition & 1 deletion src/agent/mcp-marketplace.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { writeJsonAtomic } from '../tui/persistence.js'
// this file, not process.cwd(), so installs work from any launch directory.
const PKG_SERVER = (rel) => fileURLToPath(new URL(`../../mcp-servers/${rel}`, import.meta.url))

const CATALOG_URL = 'https://axion.amplifiedsmp.org/mcp-catalog.json'
const CATALOG_URL = 'https://sennoric.com/mcp-catalog.json'
const CACHE_TTL = 60 * 60 * 1000 // 1 hour

// ── Local fallback catalog ─────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion src/agent/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export function createClient(modelAlias) {
apiKey: key,
baseURL: BASE_URLS.openrouter,
defaultHeaders: {
'HTTP-Referer': 'https://axion.amplifiedsmp.org',
'HTTP-Referer': 'https://sennoric.com',
'X-Title': 'Sennoric',
},
}) };
Expand Down
Loading
Loading