Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 5 additions & 3 deletions server/src/chat/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ export function probeableProvider(prefix: string): boolean { return PROBE_PATTER
export function providerReason(text: string): string {
let t = text
for (let i = 0; i < 3; i++) {
const m = /"message"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(t)
// A complete string first; failing that, a string cut off by an
// upstream truncation, taken to the end of what arrived.
const m = /"message"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(t) ?? /"message"\s*:\s*"((?:[^"\\]|\\.)*)$/.exec(t)
if (!m) break
t = m[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\')
}
Expand Down Expand Up @@ -305,7 +307,7 @@ export async function probeProvider(prefix: string, sampleModelId: string, key?:
const unlockUrl = extractUnlockUrl(r.text)
const credentialish = unlockUrl !== null || r.status === 401 || r.status === 403 || /key locked|locked|unauthorized|api key/i.test(r.text)
if (credentialish) {
v = { ok: false, kind: 'locked', unlockUrl, message: r.text.slice(0, 200) }
v = { ok: false, kind: 'locked', unlockUrl, message: r.text.slice(0, 2000) }
} else {
// The gateway masks a provider's own 401 into a generic 400
// (parallelworks/core#19405), so the lock this probe exists for
Expand All @@ -314,7 +316,7 @@ export async function probeProvider(prefix: string, sampleModelId: string, key?:
// for real, whatever the wording; a healthy serve essentially
// never fails it once, let alone twice.
r = await ping()
if (!r.ok) v = { ok: false, kind: 'unavailable', unlockUrl: extractUnlockUrl(r.text), message: r.text.slice(0, 200) }
if (!r.ok) v = { ok: false, kind: 'unavailable', unlockUrl: extractUnlockUrl(r.text), message: r.text.slice(0, 2000) }
}
}
} catch { /* unreachable is not proof of a lock; leave ok */ }
Expand Down
7 changes: 7 additions & 0 deletions server/test/providerProbe.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,10 @@ test('a marked model carries the reason to the client', async () => {
assert.equal(r.impaired[0].reason, 'model not served')
assert.equal(r.impaired[0].locked, false)
})

test('the reason survives a body truncated mid-message', async () => {
const { providerReason } = await import('../dist/chat/gateway.js')
// Exactly what the banner showed: the outer message cut at 200 characters.
const cut = '{"error":{"message":"received error while streaming: {\\"message\\": \\"Requested model is not available and no compliant same-model variant was found.\\", \\"type\\": \\"invalid_reque'
assert.equal(providerReason(cut), 'Requested model is not available and no compliant same-model variant was found.')
})
13 changes: 13 additions & 0 deletions web/src/layers.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Cascade layer order, declared before any stylesheet loads.
*
* The package stylesheets nest their rules in the pw-ui and pw-ai-chat
* layers, except one: the compiled ui stylesheet (0.16) also emits a
* top-level `base` layer that gives every input and textarea a border,
* padding, and background. Declared after the package layers, it
* outranked their utilities, so the chat composer grew a second border
* and the conversation search lost the padding that clears its icon.
* Naming `base` first makes it the lowest layer. The Studio's own inputs
* all carry their own classes and are unaffected.
*/
@layer base, pw-ui, pw-ai-chat;
1 change: 1 addition & 0 deletions web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import '@fontsource-variable/geist-mono'
// equal specificity, so whichever loads last wins. The chat package must come
// second: its responsive variants (a two-column starter grid, for one) were
// being overridden by the UI package's base utilities. Ours loads last of all.
import './layers.css'
import '@parallelworks/ui/styles.css'
import '@parallelworks/ui/theme.css'
import '@parallelworks/ai-chat/styles.css'
Expand Down
Loading