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
2 changes: 1 addition & 1 deletion plugins/thread-briefs/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default async function plugin(bb: BbPluginApi) {
const settings = bb.settings.define({
baseUrl: {
type: "string",
label: "API base URL",
label: "API base URL (root or full /chat/completions endpoint)",
default: "https://api.openai.com/v1",
},
apiKey: { type: "string", label: "API key", secret: true },
Expand Down
14 changes: 13 additions & 1 deletion plugins/thread-briefs/skills/thread-briefs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Set these with `bb plugin config thread-briefs set <key> <value>`.

| Key | Default | What it does |
| --- | --- | --- |
| `baseUrl` | `https://api.openai.com/v1` | OpenAI-compatible API root. `/chat/completions` is appended; a trailing slash is fine. |
| `baseUrl` | `https://api.openai.com/v1` | OpenAI-compatible endpoint. Either the API root or the full `/chat/completions` URL works; trailing slashes are fine. |
| `apiKey` | _(unset, secret)_ | Bearer token for that endpoint. The plugin reports `needs-configuration` until it is set. |
| `model` | `gpt-4o-mini` | Model used for summarizing. Any small instruction-following model works. |
| `jsonMode` | `true` | Send `response_format: {type: "json_object"}`. Turn **off** for endpoints that reject it (many local servers do). |
Expand All @@ -25,6 +25,18 @@ Set these with `bb plugin config thread-briefs set <key> <value>`.
The key is a secret setting, so it stays on the server and is never sent to the
frontend.

Worked example, Fireworks:

```sh
bb plugin config thread-briefs set baseUrl "https://api.fireworks.ai/inference/v1"
bb plugin config thread-briefs set model "accounts/fireworks/models/glm-5p3-flash"
bb plugin config thread-briefs set apiKey "<key>"
```

A 404 naming a doubled path (`/chat/completions/chat/completions`) meant an older
build appended the path to a `baseUrl` that already ended in it. Both spellings
are accepted now.

## When a brief is regenerated

1. `thread.idle` fires at every turn boundary and starts a `quietSeconds`
Expand Down
26 changes: 26 additions & 0 deletions plugins/thread-briefs/summarize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ describe("parseSummary", () => {
});

describe("chatCompletionsUrl", () => {
it("appends the path to an API root", () => {
expect(chatCompletionsUrl("https://api.openai.com/v1")).toBe(
"https://api.openai.com/v1/chat/completions",
);
});

it("tolerates a trailing slash", () => {
expect(chatCompletionsUrl("https://api.openai.com/v1/")).toBe(
"https://api.openai.com/v1/chat/completions",
Expand All @@ -88,6 +94,26 @@ describe("chatCompletionsUrl", () => {
"https://x.test/v1/chat/completions",
);
});

// Providers document the full endpoint, so it gets pasted in as the base URL.
// Appending blindly produced /chat/completions/chat/completions and a 404.
it("leaves a full Fireworks endpoint alone", () => {
expect(
chatCompletionsUrl("https://api.fireworks.ai/inference/v1/chat/completions"),
).toBe("https://api.fireworks.ai/inference/v1/chat/completions");
});

it("accepts the Fireworks API root too", () => {
expect(chatCompletionsUrl("https://api.fireworks.ai/inference/v1")).toBe(
"https://api.fireworks.ai/inference/v1/chat/completions",
);
});

it("leaves a full endpoint with a trailing slash alone", () => {
expect(chatCompletionsUrl("https://x.test/v1/chat/completions/")).toBe(
"https://x.test/v1/chat/completions",
);
});
});

describe("buildUserPrompt", () => {
Expand Down
16 changes: 14 additions & 2 deletions plugins/thread-briefs/summarize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,21 @@ export interface CompletionConfig {
jsonMode: boolean;
}

/** Trailing slashes are the single most common way a base URL is mistyped. */
/**
* Accept either the API root or the full chat-completions endpoint.
*
* Providers document the full URL — Fireworks publishes
* `https://api.fireworks.ai/inference/v1/chat/completions`, OpenAI the same
* shape — so pasting that into a setting labelled "base URL" is the natural
* mistake, and appending blindly produced
* `/v1/chat/completions/chat/completions` and a 404. Trailing slashes are the
* other common way to mistype it.
*/
export function chatCompletionsUrl(baseUrl: string): string {
return `${baseUrl.trim().replace(/\/+$/u, "")}/chat/completions`;
const trimmed = baseUrl.trim().replace(/\/+$/u, "");
return /\/chat\/completions$/u.test(trimmed)
? trimmed
: `${trimmed}/chat/completions`;
}

export async function requestSummary(
Expand Down
Loading