Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
01be738
feat(mcp): add optional model param to opera_chat tool
psrednicki May 25, 2026
c86f4df
feat(mcp): add opera_list_models tool
psrednicki May 25, 2026
f7ae844
feat(mcp): update generated CLI options for model selector
psrednicki May 25, 2026
2d0b02f
refactor(mcp): use dispatchAction for listModels instead of separate …
psrednicki May 25, 2026
5e7aefa
chore: bump version
psrednicki May 26, 2026
1f2f954
chore: bump version to 0.2.3 in package-lock.json
psrednicki May 26, 2026
dcb64bc
chore: downgrade version to 0.2.2 in package.json, package-lock.json,…
psrednicki May 27, 2026
cf45c92
fix(opera): increase service worker retry delay from 1000ms to 2500ms
psrednicki May 28, 2026
4655f82
feat(opera): add `opera_list_models` tool and update documentation
psrednicki May 28, 2026
d4eb20f
fix(opera): update description for `opera_list_models` tool to clarif…
psrednicki May 28, 2026
1869870
fix(opera): correct description for `opera_list_models` tool to indic…
psrednicki May 28, 2026
30c0972
fix(opera): remove unnecessary blank line in `opera_list_models` tool…
psrednicki May 28, 2026
de86728
fix(opera): remove trailing comma in `opera_list_models` tool documen…
psrednicki May 28, 2026
dc0e661
chore(ci): add step to disable AppArmor for CI environment
psrednicki May 28, 2026
eb67907
chore(ci): remove Node.js version 23 from CI workflow
psrednicki May 28, 2026
622dab8
chore(ci): update Node.js versions and add Puppeteer browser installa…
psrednicki May 28, 2026
b291258
fix(ci): ensure proper exit status handling in CI workflow
psrednicki May 28, 2026
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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ jobs:
cache: npm

- run: npm ci

- name: Disable AppArmor
shell: bash
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns

- run: npm test
10 changes: 8 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
- windows-latest
- macos-latest
node:
- 20
- 22
- 23
- 24
- 26

steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand All @@ -40,6 +40,12 @@ jobs:
- name: Install dependencies
shell: bash
run: npm ci
env:
PUPPETEER_SKIP_DOWNLOAD: true

- name: Install browser
shell: bash
run: npx puppeteer browsers install chrome

- name: Build
run: npm run bundle
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,10 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
- [`get_memory_snapshot_details`](docs/tool-reference.md#get_memory_snapshot_details)
- [`get_nodes_by_class`](docs/tool-reference.md#get_nodes_by_class)
- [`load_memory_snapshot`](docs/tool-reference.md#load_memory_snapshot)
- **Opera** (4 tools)
- **Opera** (5 tools)
- [`opera_chat`](docs/tool-reference.md#opera_chat)
- [`opera_do`](docs/tool-reference.md#opera_do)
- [`opera_list_models`](docs/tool-reference.md#opera_list_models)
- [`opera_make`](docs/tool-reference.md#opera_make)
- [`opera_research`](docs/tool-reference.md#opera_research)
- **Extensions** (5 tools)
Expand Down
12 changes: 11 additions & 1 deletion docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@
- [`get_memory_snapshot_details`](#get_memory_snapshot_details)
- [`get_nodes_by_class`](#get_nodes_by_class)
- [`load_memory_snapshot`](#load_memory_snapshot)
- **[Opera](#opera)** (4 tools)
- **[Opera](#opera)** (5 tools)
- [`opera_chat`](#opera_chat)
- [`opera_do`](#opera_do)
- [`opera_list_models`](#opera_list_models)
- [`opera_make`](#opera_make)
- [`opera_research`](#opera_research)
- **[Extensions](#extensions)** (5 tools)
Expand Down Expand Up @@ -500,6 +501,7 @@ in the DevTools Elements panel (if any).
**Parameters:**

- **prompt** (string) **(required)**: The prompt to send to Opera AI.
- **model** (string) _(optional)_: Model ID to use for the chat. Omit to use the browser default. Use [`opera_list_models`](#opera_list_models) to discover available IDs.

---

Expand All @@ -513,6 +515,14 @@ in the DevTools Elements panel (if any).

---

### `opera_list_models`

**Description:** List available AI models for Opera chat. Returns model IDs, display names, and which is the default. Only available when connected to Opera Neon.

**Parameters:** None

---

### `opera_make`

**Description:** Ask Opera's built-in AI to create or generate content and return the result. Only available when connected to Opera Neon.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/bin/chrome-devtools-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,13 @@ export const commands: Commands = {
description: 'The prompt to send to Opera AI.',
required: true,
},
model: {
name: 'model',
type: 'string',
description:
'Model ID to use for the chat. Omit to use the browser default. Use opera_list_models to discover available IDs.',
required: false,
},
},
},
opera_do: {
Expand All @@ -492,6 +499,12 @@ export const commands: Commands = {
},
},
},
opera_list_models: {
description:
'List available AI models for Opera chat. Returns model IDs, display names, and which is the default. Only available when connected to Opera Neon.',
category: 'Opera',
args: {},
},
opera_make: {
description:
"Ask Opera's built-in AI to create or generate content and return the result. Only available when connected to Opera Neon.",
Expand Down
8 changes: 8 additions & 0 deletions src/telemetry/tool_call_metrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,10 @@
{
"name": "prompt_length",
"argType": "number"
},
{
"name": "model_length",
"argType": "number"
}
]
},
Expand Down Expand Up @@ -667,5 +671,9 @@
"argType": "string"
}
]
},
{
"name": "opera_list_models",
"args": []
}
]
40 changes: 37 additions & 3 deletions src/tools/opera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const getCDPSession = (page: {_client(): CDPSession}): CDPSession =>
page._client();

const MAX_SW_RETRIES = 5;
const SW_RETRY_DELAY_MS = 1000;
const SW_RETRY_DELAY_MS = 2500;

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

Expand Down Expand Up @@ -129,15 +129,25 @@ export const operaChat = definePageTool({
},
schema: {
prompt: zod.string().describe('The prompt to send to Opera AI.'),
model: zod
.string()
.optional()
.describe(
'Model ID to use for the chat. Omit to use the browser default. Use opera_list_models to discover available IDs.',
),
},
handler: async (request, response) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const session = getCDPSession(request.page.pptrPage as any);
try {
const result = await dispatchAction(session, {
const payload: Record<string, unknown> = {
action: 'chat',
prompt: request.params.prompt,
});
};
if (request.params.model !== undefined) {
payload['model'] = request.params.model;
}
const result = await dispatchAction(session, payload);
response.appendResponseLine(result);
} catch (e) {
response.appendResponseLine(
Expand Down Expand Up @@ -261,3 +271,27 @@ export const operaResearch = definePageTool({
}
},
});

export const operaListModels = definePageTool({
name: 'opera_list_models',
description:
'List available AI models for Opera chat. Returns model IDs, display names, and which is the default. Only available when connected to Opera Neon.',
blockedByDialog: false,
annotations: {
category: ToolCategory.OPERA,
readOnlyHint: true,
},
schema: {},
handler: async (request, response) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const session = getCDPSession(request.page.pptrPage as any);
try {
const result = await dispatchAction(session, {action: 'listModels'});
response.appendResponseLine(result);
} catch (e) {
response.appendResponseLine(
`Opera.dispatchAction(listModels) failed with error: ${(e as Error).message}`,
);
}
},
});
Loading