-
Notifications
You must be signed in to change notification settings - Fork 0
feat(app): branded Harmoniqs AI connect entry point in the generic Connect Provider dialog #327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { describe, expect, test, beforeEach, afterEach } from "bun:test" | ||
| import { | ||
| HARMONIQS_PROVIDER_ID, | ||
| HARMONIQS_PROVIDER_NAME, | ||
| CONNECT_HARMONIQS_PROVIDER_KIND, | ||
| CONNECT_HARMONIQS_PROVIDER_ACK_KIND, | ||
| shouldShowHarmoniqsEntry, | ||
| requestHarmoniqsProviderConnect, | ||
| isHarmoniqsProviderConnectAck, | ||
| } from "./dialog-connect-provider-harmoniqs" | ||
|
|
||
| // `inAmicode()` gates on `window.self !== window.top` — these tests fake an | ||
| // iframe by overriding `window.top` to a distinct object, and restore it | ||
| // afterward so other test files' `window` state is untouched. | ||
| function frame(): () => void { | ||
| const realTop = window.top | ||
| Object.defineProperty(window, "top", { value: {}, configurable: true }) | ||
| return () => Object.defineProperty(window, "top", { value: realTop, configurable: true }) | ||
| } | ||
|
|
||
| describe("shouldShowHarmoniqsEntry", () => { | ||
| test("hidden when unframed (no extension host to relay to)", () => { | ||
| expect(shouldShowHarmoniqsEntry(new Set())).toBe(false) | ||
| }) | ||
|
|
||
| test("shown when framed and the real provider hasn't landed in the catalog", () => { | ||
| const unframe = frame() | ||
| expect(shouldShowHarmoniqsEntry(new Set(["anthropic", "openai"]))).toBe(true) | ||
| unframe() | ||
| }) | ||
|
|
||
| test('hidden when a real "harmoniqs" catalog entry already exists — the stub never shadows it', () => { | ||
| const unframe = frame() | ||
| expect(shouldShowHarmoniqsEntry(new Set([HARMONIQS_PROVIDER_ID]))).toBe(false) | ||
| unframe() | ||
| }) | ||
| }) | ||
|
|
||
| describe("requestHarmoniqsProviderConnect", () => { | ||
| let posted: unknown[] = [] | ||
| let unframe: () => void | ||
| let restorePost: () => void | ||
|
|
||
| beforeEach(() => { | ||
| posted = [] | ||
| unframe = frame() | ||
| const original = window.parent.postMessage.bind(window.parent) | ||
| window.parent.postMessage = ((msg: unknown) => posted.push(msg)) as typeof window.parent.postMessage | ||
| restorePost = () => { | ||
| window.parent.postMessage = original | ||
| } | ||
| }) | ||
|
|
||
| afterEach(() => { | ||
| restorePost() | ||
| unframe() | ||
| }) | ||
|
|
||
| test("posts the connect envelope, not a generic provider-connect message", () => { | ||
| requestHarmoniqsProviderConnect() | ||
| expect(posted).toEqual([{ source: "amicode", kind: CONNECT_HARMONIQS_PROVIDER_KIND }]) | ||
| }) | ||
|
|
||
| test("never carries a provider id/config payload the generic ProviderConnection flow would expect", () => { | ||
| requestHarmoniqsProviderConnect() | ||
| const msg = posted[0] as Record<string, unknown> | ||
| expect(msg).not.toHaveProperty("provider") | ||
| expect(msg).not.toHaveProperty("apiKey") | ||
| expect(msg).not.toHaveProperty("baseURL") | ||
| }) | ||
| }) | ||
|
|
||
| describe("isHarmoniqsProviderConnectAck", () => { | ||
| test("recognizes the extension's ack envelope", () => { | ||
| expect(isHarmoniqsProviderConnectAck({ source: "amicode", kind: CONNECT_HARMONIQS_PROVIDER_ACK_KIND })).toBe(true) | ||
| }) | ||
|
|
||
| test("rejects foreign or malformed messages", () => { | ||
| expect(isHarmoniqsProviderConnectAck(undefined)).toBe(false) | ||
| expect(isHarmoniqsProviderConnectAck(null)).toBe(false) | ||
| expect(isHarmoniqsProviderConnectAck("connect-harmoniqs-provider-ack")).toBe(false) | ||
| expect(isHarmoniqsProviderConnectAck({ source: "amicode", kind: "dev-tools-status" })).toBe(false) | ||
| expect(isHarmoniqsProviderConnectAck({ source: "other", kind: CONNECT_HARMONIQS_PROVIDER_ACK_KIND })).toBe(false) | ||
| }) | ||
| }) | ||
|
|
||
| describe("constants", () => { | ||
| test("HARMONIQS_PROVIDER_NAME is the branded display name", () => { | ||
| expect(HARMONIQS_PROVIDER_NAME).toBe("Harmoniqs AI") | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // Harmoniqs AI — a branded, always-present entry in the Connect Provider | ||
| // picker (amicode#962). It is deliberately NOT sourced from providers().all(): | ||
| // Harmoniqs is a preset (fixed base URL/model, key routed straight to | ||
| // opencode's own auth store) that the generic key-entry flow | ||
| // (ProviderConnection in dialog-connect-provider.tsx) cannot express without | ||
| // duplicating that logic — see packages/extension/src/onboarding_panel.ts | ||
| // (amicode repo) for where writeOnboardingConfig/writeAuthApiKey/ | ||
| // testConnection/classifyHarmoniqsError actually live. Clicking this entry | ||
| // hands off to that logic's own connection UI instead of rendering here. | ||
| // | ||
| // Extracted from dialog-connect-provider.tsx (mirrors dialog-custom-provider's | ||
| // split into dialog-custom-provider-form.ts) so the gating/messaging logic is | ||
| // unit-testable without rendering the picker. | ||
|
|
||
| import { inAmicode } from "@/utils/amicode-bridge" | ||
|
|
||
| export const HARMONIQS_PROVIDER_ID = "harmoniqs" | ||
| export const HARMONIQS_PROVIDER_NAME = "Harmoniqs AI" | ||
|
|
||
| /** The envelope this module posts when the branded row is clicked. */ | ||
| export const CONNECT_HARMONIQS_PROVIDER_KIND = "connect-harmoniqs-provider" | ||
| /** The envelope the extension host acks with — the dialog closes on receipt | ||
| * (see useHarmoniqsProviderConnectAck). The extension's own handoff panel | ||
| * (Stage-0's onboarding webview, focused to just this provider) runs | ||
| * independently after that; this dialog has no further role. */ | ||
| export const CONNECT_HARMONIQS_PROVIDER_ACK_KIND = "connect-harmoniqs-provider-ack" | ||
|
|
||
| type KnownProviderIds = ReadonlySet<string> | { has(id: string): boolean } | ||
|
|
||
| /** Whether the branded Harmoniqs row belongs in the picker right now: | ||
| * - only inside the extension (`inAmicode()`) — outside it there is no host | ||
| * to relay `connect-harmoniqs-provider` to, so a visible-but-dead entry | ||
| * would just confuse; and | ||
| * - only while the real provider hasn't already landed in the catalog some | ||
| * other way (a future `provider.harmoniqs` entry in opencode.json, or the | ||
| * generic seed mechanism tracked separately as amicode#326) — the stub | ||
| * must never shadow a real catalog entry. */ | ||
| export function shouldShowHarmoniqsEntry(knownProviderIds: KnownProviderIds): boolean { | ||
| return inAmicode() && !knownProviderIds.has(HARMONIQS_PROVIDER_ID) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 10110 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
cat -n packages/app/src/components/dialog-connect-provider-harmoniqs.ts
printf '%s\n' '--- inAmicode definitions and uses ---'
rg -n -C 4 'inAmicode|shouldShowHarmoniqsEntry|HARMONIQS_PROVIDER_ID|postMessage' packages/app/src
printf '%s\n' '--- framing and security policy references ---'
rg -n -C 3 'frame-ancestors|X-Frame-Options|iframe|Amicode|amicode' --glob '!node_modules' --glob '!dist' --glob '!build' .Repository: harmoniqs/opencode Length of output: 50375 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target ---'
cat -n packages/app/src/components/dialog-connect-provider-harmoniqs.ts
printf '%s\n' '--- inAmicode bindings ---'
rg -n -C 8 'export (function|const) inAmicode|function inAmicode|const inAmicode' packages/app/src/utils packages/app/src/pages/session
printf '%s\n' '--- target imports and direct callers ---'
rg -n -C 5 'dialog-connect-provider-harmoniqs|shouldShowHarmoniqsEntry|requestHarmoniqsProviderConnect' packages/app/src
printf '%s\n' '--- relevant tests ---'
cat -n packages/app/src/components/dialog-connect-provider-harmoniqs.test.ts
printf '%s\n' '--- framing policy files ---'
rg -l -m 1 'frame-ancestors|X-Frame-Options|Content-Security-Policy' packages/app .github 2>/dev/null | head -50Repository: harmoniqs/opencode Length of output: 22824 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- tracked framing policy declarations ---'
git grep -n -E 'frame-ancestors|X-Frame-Options|Content-Security-Policy' -- ':!packages/app/src/components/dialog-connect-provider-harmoniqs.test.ts' || true
printf '%s\n' '--- tracked Amicode host/bridge handlers ---'
git grep -n -E 'connect-harmoniqs-provider|amicode.*(iframe|frame)|iframe.*amicode|postMessage.*source.*amicode' -- \
'packages/extension/**' 'packages/app/**' 'README*' 'docs/**' 2>/dev/null | head -120 || true
printf '%s\n' '--- relevant bridge implementation ---'
cat -n packages/app/src/utils/amicode-bridge.tsRepository: harmoniqs/opencode Length of output: 8776 Use an Amicode-specific predicate for the Harmoniqs entry. When a non-Amicode parent frames the app, 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| /** Ask the extension host to open its own Harmoniqs connection UI. */ | ||
| export function requestHarmoniqsProviderConnect(): void { | ||
| if (typeof window === "undefined") return | ||
| window.parent?.postMessage({ source: "amicode", kind: CONNECT_HARMONIQS_PROVIDER_KIND }, "*") | ||
| } | ||
|
|
||
| /** True for the extension's ack envelope — the dialog's cue to close. */ | ||
| export function isHarmoniqsProviderConnectAck(data: unknown): boolean { | ||
| if (!data || typeof data !== "object") return false | ||
| const d = data as { source?: unknown; kind?: unknown } | ||
| return d.source === "amicode" && d.kind === CONNECT_HARMONIQS_PROVIDER_ACK_KIND | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Restore
window.topwhen an assertion fails.If either assertion fails, its
unframe()call does not run. Later tests then inherit the synthetic iframe state. Usetry/finally, or restore the state inafterEach.Proposed fix
Also applies to: 33-35
🤖 Prompt for AI Agents