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
11 changes: 8 additions & 3 deletions apps/web/src/app/opencode/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { opencodePluginVersion } from "@/lib/opencode-version";
const npmUrl = "https://www.npmjs.com/package/@sketchi-app/opencode-excalidraw";
const githubUrl =
"https://github.com/anand-testcompare/sketchi/tree/main/packages/opencode-excalidraw";
const authCommand = "opencode auth login";
const webCommand = "opencode web";
const cliCommand = "opencode";

Expand Down Expand Up @@ -284,11 +285,15 @@ export default function OpenCodeDocsPage() {
.
</li>
<li>
Run <code>opencode web</code>.
Run <code>{authCommand}</code> and select <code>sketchi</code>{" "}
(device-flow sign-in).
</li>
<li>
Then switch to <code>opencode</code> and run again in terminal
mode.
Run <code>{webCommand}</code>.
</li>
<li>
Then switch to <code>{cliCommand}</code> and run again in
terminal mode.
</li>
</ol>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode-excalidraw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ When this plugin is loaded, it registers a `sketchi-diagram` subagent (without d

Optional env override:

- `SKETCHI_API_URL` (defaults to `https://sketchi.app`)
- `SKETCHI_API_URL` (defaults to `https://www.sketchi.app`)
- `SKETCHI_ALLOW_UNSAFE_OUTPUT_PATH=1` to opt out of default output-path containment (power users only)

Authentication is required for diagram APIs. In OpenCode, select auth provider `sketchi`:
Expand Down
52 changes: 52 additions & 0 deletions packages/opencode-excalidraw/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,58 @@ describe("SketchiPlugin", () => {
expect(method?.label.toLowerCase()).toContain("device flow");
});

test("canonicalizes sketchi.app API base to www for auth device start", async () => {
const originalFetch = globalThis.fetch;
const originalApiBase = process.env.SKETCHI_API_URL;
const requestedUrls: string[] = [];

globalThis.fetch = ((input) => {
let url: string;
if (typeof input === "string") {
url = input;
} else if (input instanceof URL) {
url = input.toString();
} else {
url = input.url;
}
requestedUrls.push(url);
return new Response(
JSON.stringify({
deviceCode: "device-code",
userCode: "ABCD-EFGH",
interval: 5,
expiresIn: 600,
verificationUrl: "https://www.sketchi.app/device",
}),
{
status: 200,
headers: { "content-type": "application/json" },
}
);
}) as typeof fetch;

process.env.SKETCHI_API_URL = "https://sketchi.app/";

try {
const plugin = await SketchiPlugin(createPluginInput());
const method = plugin.auth?.methods?.[0];
expect(method?.type).toBe("oauth");

const authStart = await method?.authorize();
expect(authStart?.url).toBe("https://www.sketchi.app/device");
expect(requestedUrls[0]).toBe(
"https://www.sketchi.app/api/auth/device/start"
);
} finally {
globalThis.fetch = originalFetch;
if (originalApiBase === undefined) {
process.env.SKETCHI_API_URL = undefined;
} else {
process.env.SKETCHI_API_URL = originalApiBase;
}
}
});

test("diagram_grade blocks concurrent calls for the same message", async () => {
const deferred = createDeferred<{
data: { parts: Array<{ type: string; text: string }> };
Expand Down
15 changes: 13 additions & 2 deletions packages/opencode-excalidraw/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { closeBrowser, renderElementsToPng } from "./lib/render";
import { resolveExcalidrawFromShareUrl } from "./lib/resolve-share-url";
import { createToolTraceId } from "./lib/trace";

const DEFAULT_API_BASE = "https://sketchi.app";
const DEFAULT_API_BASE = "https://www.sketchi.app";
const DEFAULT_OAUTH_TOKEN_TTL_MS = 60 * 60 * 1000;
const DEVICE_FLOW_POLLING_SAFETY_MARGIN_MS = 1000;
const TRAILING_SLASH_PATTERN = /\/$/;
Expand Down Expand Up @@ -149,7 +149,18 @@ function completeGradeCall(key: string): void {
}

function normalizeApiBase(value: string): string {
return value.replace(TRAILING_SLASH_PATTERN, "");
const trimmed = value.trim();
const withoutTrailingSlash = trimmed.replace(TRAILING_SLASH_PATTERN, "");

try {
const parsed = new URL(withoutTrailingSlash);
if (parsed.hostname === "sketchi.app") {
parsed.hostname = "www.sketchi.app";
}
return parsed.toString().replace(TRAILING_SLASH_PATTERN, "");
} catch {
return withoutTrailingSlash;
}
}

function extractMessageText(
Expand Down
Loading