@rune-kit/rune 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +8 -6
- package/commands/rune.md +168 -168
- package/contexts/dev.md +34 -34
- package/contexts/research.md +43 -43
- package/contexts/review.md +55 -55
- package/extensions/ai-ml/PACK.md +88 -88
- package/extensions/ai-ml/skills/ai-agents.md +172 -172
- package/extensions/ai-ml/skills/code-sandbox.md +187 -187
- package/extensions/ai-ml/skills/deep-research.md +146 -146
- package/extensions/ai-ml/skills/embedding-search.md +66 -66
- package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
- package/extensions/ai-ml/skills/llm-architect.md +125 -125
- package/extensions/ai-ml/skills/llm-integration.md +64 -64
- package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
- package/extensions/ai-ml/skills/rag-patterns.md +66 -66
- package/extensions/ai-ml/skills/web-extraction.md +114 -114
- package/extensions/analytics/PACK.md +92 -92
- package/extensions/analytics/skills/ab-testing.md +72 -72
- package/extensions/analytics/skills/dashboard-patterns.md +83 -83
- package/extensions/analytics/skills/data-validation.md +68 -68
- package/extensions/analytics/skills/funnel-analysis.md +81 -81
- package/extensions/analytics/skills/sql-patterns.md +57 -57
- package/extensions/analytics/skills/statistical-analysis.md +79 -79
- package/extensions/analytics/skills/tracking-setup.md +71 -71
- package/extensions/backend/PACK.md +104 -104
- package/extensions/backend/skills/api-patterns.md +84 -84
- package/extensions/backend/skills/async-pipeline.md +193 -193
- package/extensions/backend/skills/auth-patterns.md +97 -97
- package/extensions/backend/skills/background-jobs.md +133 -133
- package/extensions/backend/skills/caching-patterns.md +108 -108
- package/extensions/backend/skills/cli-generation.md +133 -133
- package/extensions/backend/skills/database-patterns.md +87 -87
- package/extensions/backend/skills/middleware-patterns.md +104 -104
- package/extensions/chrome-ext/PACK.md +93 -93
- package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
- package/extensions/chrome-ext/skills/cws-publish.md +104 -104
- package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
- package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
- package/extensions/chrome-ext/skills/ext-storage.md +133 -133
- package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
- package/extensions/content/PACK.md +96 -96
- package/extensions/content/skills/blog-patterns.md +88 -88
- package/extensions/content/skills/cms-integration.md +131 -131
- package/extensions/content/skills/content-scoring.md +107 -107
- package/extensions/content/skills/i18n.md +83 -83
- package/extensions/content/skills/mdx-authoring.md +137 -137
- package/extensions/content/skills/reference.md +1014 -1014
- package/extensions/content/skills/seo-patterns.md +67 -67
- package/extensions/content/skills/video-repurpose.md +153 -153
- package/extensions/devops/PACK.md +101 -101
- package/extensions/devops/skills/chaos-testing.md +67 -67
- package/extensions/devops/skills/ci-cd.md +75 -75
- package/extensions/devops/skills/docker.md +58 -58
- package/extensions/devops/skills/edge-serverless.md +163 -163
- package/extensions/devops/skills/infra-as-code.md +158 -158
- package/extensions/devops/skills/kubernetes.md +110 -110
- package/extensions/devops/skills/monitoring.md +57 -57
- package/extensions/devops/skills/server-setup.md +64 -64
- package/extensions/devops/skills/ssl-domain.md +42 -42
- package/extensions/ecommerce/PACK.md +116 -116
- package/extensions/ecommerce/skills/cart-system.md +79 -79
- package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
- package/extensions/ecommerce/skills/order-management.md +126 -126
- package/extensions/ecommerce/skills/payment-integration.md +472 -472
- package/extensions/ecommerce/skills/shopify-dev.md +69 -69
- package/extensions/ecommerce/skills/subscription-billing.md +93 -93
- package/extensions/ecommerce/skills/tax-compliance.md +117 -117
- package/extensions/gamedev/PACK.md +142 -142
- package/extensions/gamedev/skills/asset-pipeline.md +74 -74
- package/extensions/gamedev/skills/audio-system.md +129 -129
- package/extensions/gamedev/skills/camera-system.md +87 -87
- package/extensions/gamedev/skills/ecs.md +98 -98
- package/extensions/gamedev/skills/game-loops.md +72 -72
- package/extensions/gamedev/skills/input-system.md +199 -199
- package/extensions/gamedev/skills/multiplayer.md +180 -180
- package/extensions/gamedev/skills/particles.md +105 -105
- package/extensions/gamedev/skills/physics-engine.md +89 -89
- package/extensions/gamedev/skills/scene-management.md +146 -146
- package/extensions/gamedev/skills/threejs-patterns.md +90 -90
- package/extensions/gamedev/skills/webgl.md +71 -71
- package/extensions/mobile/PACK.md +106 -106
- package/extensions/mobile/skills/app-store-connect.md +152 -152
- package/extensions/mobile/skills/app-store-prep.md +66 -66
- package/extensions/mobile/skills/deep-linking.md +109 -109
- package/extensions/mobile/skills/flutter.md +60 -60
- package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
- package/extensions/mobile/skills/native-bridge.md +66 -66
- package/extensions/mobile/skills/ota-updates.md +97 -97
- package/extensions/mobile/skills/push-notifications.md +111 -111
- package/extensions/mobile/skills/react-native.md +82 -82
- package/extensions/saas/PACK.md +116 -116
- package/extensions/saas/skills/billing-integration.md +200 -200
- package/extensions/saas/skills/feature-flags.md +130 -130
- package/extensions/saas/skills/multi-tenant.md +103 -103
- package/extensions/saas/skills/onboarding-flow.md +139 -139
- package/extensions/saas/skills/subscription-flow.md +95 -95
- package/extensions/saas/skills/team-management.md +144 -144
- package/extensions/security/PACK.md +99 -99
- package/extensions/security/skills/api-security.md +140 -140
- package/extensions/security/skills/compliance.md +68 -68
- package/extensions/security/skills/owasp-audit.md +64 -64
- package/extensions/security/skills/pentest-patterns.md +77 -77
- package/extensions/security/skills/secret-mgmt.md +65 -65
- package/extensions/security/skills/supply-chain.md +65 -65
- package/extensions/trading/PACK.md +80 -80
- package/extensions/trading/skills/chart-components.md +55 -55
- package/extensions/trading/skills/experiment-loop.md +125 -125
- package/extensions/trading/skills/fintech-patterns.md +47 -47
- package/extensions/trading/skills/indicator-library.md +58 -58
- package/extensions/trading/skills/quant-analysis.md +111 -111
- package/extensions/trading/skills/realtime-data.md +58 -58
- package/extensions/trading/skills/trade-logic.md +104 -104
- package/extensions/ui/PACK.md +130 -130
- package/extensions/ui/skills/a11y-audit.md +91 -91
- package/extensions/ui/skills/animation-patterns.md +127 -127
- package/extensions/ui/skills/component-patterns.md +100 -100
- package/extensions/ui/skills/design-decision.md +108 -108
- package/extensions/ui/skills/design-system.md +68 -68
- package/extensions/ui/skills/landing-patterns.md +155 -155
- package/extensions/ui/skills/palette-picker.md +173 -173
- package/extensions/ui/skills/react-health.md +90 -90
- package/extensions/ui/skills/type-system.md +125 -125
- package/extensions/ui/skills/web-vitals.md +153 -153
- package/extensions/zalo/PACK.md +145 -145
- package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
- package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
- package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
- package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
- package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
- package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
- package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
- package/hooks/auto-format/index.cjs +48 -48
- package/hooks/hooks.json +111 -111
- package/hooks/post-session-reflect/index.cjs +189 -189
- package/hooks/pre-compact/index.cjs +95 -95
- package/hooks/run-hook.cmd +1 -1
- package/hooks/secrets-scan/index.cjs +100 -100
- package/hooks/session-start/index.cjs +71 -71
- package/hooks/typecheck/index.cjs +65 -65
- package/package.json +63 -63
- package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
- package/references/ui-pro-max-data/charts.csv +26 -26
- package/references/ui-pro-max-data/colors.csv +161 -161
- package/references/ui-pro-max-data/styles.csv +68 -68
- package/references/ui-pro-max-data/typography.csv +74 -74
- package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
- package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
- package/skills/adversary/SKILL.md +283 -283
- package/skills/asset-creator/SKILL.md +157 -157
- package/skills/audit/SKILL.md +147 -2
- package/skills/autopsy/SKILL.md +335 -335
- package/skills/brainstorm/SKILL.md +342 -342
- package/skills/browser-pilot/SKILL.md +168 -168
- package/skills/constraint-check/SKILL.md +165 -165
- package/skills/context-engine/SKILL.md +404 -404
- package/skills/cook/SKILL.md +917 -863
- package/skills/db/SKILL.md +273 -273
- package/skills/debug/SKILL.md +465 -465
- package/skills/dependency-doctor/SKILL.md +265 -235
- package/skills/deploy/SKILL.md +274 -231
- package/skills/design/DESIGN-REFERENCE.md +365 -365
- package/skills/design/SKILL.md +589 -589
- package/skills/doc-processor/SKILL.md +254 -254
- package/skills/docs/SKILL.md +374 -374
- package/skills/docs-seeker/SKILL.md +177 -177
- package/skills/fix/SKILL.md +330 -330
- package/skills/git/SKILL.md +339 -339
- package/skills/hallucination-guard/SKILL.md +219 -219
- package/skills/incident/SKILL.md +254 -253
- package/skills/integrity-check/SKILL.md +169 -169
- package/skills/journal/SKILL.md +240 -240
- package/skills/launch/SKILL.md +344 -344
- package/skills/logic-guardian/SKILL.md +251 -251
- package/skills/marketing/SKILL.md +290 -289
- package/skills/mcp-builder/SKILL.md +425 -425
- package/skills/neural-memory/SKILL.md +362 -362
- package/skills/onboard/SKILL.md +404 -403
- package/skills/perf/SKILL.md +346 -346
- package/skills/plan/SKILL.md +433 -428
- package/skills/preflight/SKILL.md +415 -415
- package/skills/problem-solver/SKILL.md +380 -284
- package/skills/rescue/SKILL.md +474 -474
- package/skills/retro/SKILL.md +3 -1
- package/skills/review/SKILL.md +612 -588
- package/skills/review-intake/SKILL.md +249 -249
- package/skills/safeguard/SKILL.md +200 -200
- package/skills/sast/SKILL.md +190 -190
- package/skills/scaffold/SKILL.md +328 -287
- package/skills/scope-guard/SKILL.md +180 -180
- package/skills/scout/SKILL.md +263 -263
- package/skills/sentinel/SKILL.md +382 -381
- package/skills/sentinel-env/SKILL.md +254 -254
- package/skills/sequential-thinking/SKILL.md +234 -234
- package/skills/session-bridge/SKILL.md +543 -543
- package/skills/skill-forge/SKILL.md +581 -581
- package/skills/skill-router/SKILL.md +3 -0
- package/skills/surgeon/SKILL.md +215 -215
- package/skills/team/SKILL.md +556 -537
- package/skills/test/SKILL.md +614 -614
- package/skills/trend-scout/SKILL.md +145 -145
- package/skills/verification/SKILL.md +326 -326
- package/skills/video-creator/SKILL.md +201 -201
- package/skills/watchdog/SKILL.md +168 -168
- package/skills/worktree/SKILL.md +140 -140
|
@@ -1,251 +1,251 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "ext-ai-integration"
|
|
3
|
-
pack: "@rune/chrome-ext"
|
|
4
|
-
description: "Chrome built-in AI and external API integration — detect AI type, check hardware requirements, implement Gemini Nano with graceful fallback, wire streaming responses via ports, handle rate limits, and test offline behavior."
|
|
5
|
-
model: sonnet
|
|
6
|
-
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# ext-ai-integration
|
|
10
|
-
|
|
11
|
-
Chrome built-in AI and external API integration — detect AI type, check hardware requirements, implement Gemini Nano with graceful fallback, wire streaming responses via ports, handle rate limits, and test offline behavior. The differentiating skill for next-generation extensions.
|
|
12
|
-
|
|
13
|
-
**Chrome AI APIs (Chrome 138+ stable):**
|
|
14
|
-
| API | Namespace | Purpose |
|
|
15
|
-
|-----|-----------|---------|
|
|
16
|
-
| Prompt API | `chrome.aiLanguageModel` | General text generation, Q&A, classification |
|
|
17
|
-
| Summarizer | `chrome.aiSummarizer` | Condense long text |
|
|
18
|
-
| Writer | `chrome.aiWriter` | Generate new content from prompts |
|
|
19
|
-
| Rewriter | `chrome.aiRewriter` | Transform existing text (tone, length, format) |
|
|
20
|
-
| Translator | `chrome.aiTranslator` | Language translation |
|
|
21
|
-
| Language Detector | `chrome.aiLanguageDetector` | Detect text language |
|
|
22
|
-
|
|
23
|
-
**Hardware requirements for Gemini Nano:**
|
|
24
|
-
- Storage: 22 GB free disk space (model download)
|
|
25
|
-
- RAM: 4 GB VRAM (dedicated GPU) OR 16 GB system RAM (CPU inference)
|
|
26
|
-
- OS: macOS 13+, Windows 10/11 64-bit, ChromeOS (no Linux support)
|
|
27
|
-
- Cannot be checked programmatically — use capability API and handle `NotSupportedError`
|
|
28
|
-
|
|
29
|
-
**Manifest permission:**
|
|
30
|
-
```json
|
|
31
|
-
{ "permissions": ["aiLanguageModelParams"] }
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
#### Workflow
|
|
35
|
-
|
|
36
|
-
**Step 1 — Detect AI integration type**
|
|
37
|
-
Use `Read` on existing source and `manifest.json` to determine:
|
|
38
|
-
- Does `"aiLanguageModelParams"` appear in permissions? → Built-in Nano intended
|
|
39
|
-
- Does code reference `openai`, `anthropic`, `fetch` to an external AI endpoint? → External API
|
|
40
|
-
- Neither? → Need to design integration from scratch
|
|
41
|
-
|
|
42
|
-
Ask the user: "Do you want to use Chrome's built-in Gemini Nano (no API cost, runs on device, requires Chrome 138+ and compatible hardware), an external API (OpenAI/Anthropic, requires API key and network), or both with automatic fallback?"
|
|
43
|
-
|
|
44
|
-
**Step 2 — Check hardware capability for Nano**
|
|
45
|
-
`chrome.aiLanguageModel.capabilities()` returns `{ available: 'readily' | 'after-download' | 'no' }`. Map these:
|
|
46
|
-
- `'readily'` → model is downloaded, use immediately
|
|
47
|
-
- `'after-download'` → model needs download (~2GB), show progress UI and wait
|
|
48
|
-
- `'no'` → hardware not supported, fall through to fallback
|
|
49
|
-
|
|
50
|
-
This check MUST happen in the service worker (not content script — restricted APIs). Cache the result in `chrome.storage.session` to avoid repeated capability checks.
|
|
51
|
-
|
|
52
|
-
**Step 3 — Implement with graceful fallback chain**
|
|
53
|
-
Fallback chain: Gemini Nano → External API → Static response
|
|
54
|
-
|
|
55
|
-
Each tier is a distinct function with the same signature. The orchestrator tries each in order, catching `NotSupportedError`, network errors, and quota errors.
|
|
56
|
-
|
|
57
|
-
**Step 4 — Wire streaming responses via port messaging**
|
|
58
|
-
AI streaming MUST use ports — not `sendMessage`. `sendMessage` is one-shot: the response is sent once and the channel closes. Streaming requires a port to send multiple `CHUNK` messages followed by a `DONE` message.
|
|
59
|
-
|
|
60
|
-
See `ext-messaging` skill for port setup. Streaming pattern:
|
|
61
|
-
1. Sidebar/popup opens a port named `'ai-stream'`
|
|
62
|
-
2. Sends `{ text: inputText }` to start generation
|
|
63
|
-
3. Service worker receives, calls `session.promptStreaming()`
|
|
64
|
-
4. For each chunk in the async iterator, posts `{ type: 'CHUNK', content: chunk }` back on the port
|
|
65
|
-
5. On completion, posts `{ type: 'DONE' }` and calls `session.destroy()`
|
|
66
|
-
|
|
67
|
-
**Step 5 — Handle rate limits and quota**
|
|
68
|
-
Chrome built-in AI has per-session token limits. External APIs have rate limits and cost.
|
|
69
|
-
- Per session: call `session.destroy()` after each summary to free context window
|
|
70
|
-
- External API: implement exponential backoff on 429 responses (1s, 2s, 4s, cap 30s)
|
|
71
|
-
- User-facing: show token usage in settings panel if using external API
|
|
72
|
-
|
|
73
|
-
**Step 6 — Test offline behavior**
|
|
74
|
-
Extensions may run without network. Test:
|
|
75
|
-
- Built-in Nano: works offline (on-device model)
|
|
76
|
-
- External API: fails offline — catch `TypeError: Failed to fetch` and show "No network connection" message
|
|
77
|
-
- Storage: `chrome.storage.local` works offline
|
|
78
|
-
- Service worker: registers and responds to messages offline
|
|
79
|
-
|
|
80
|
-
#### Example
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// src/lib/ai.ts — AI integration with graceful fallback
|
|
84
|
-
import { storageGet } from './storage';
|
|
85
|
-
|
|
86
|
-
export interface AiSummaryResult {
|
|
87
|
-
summary: string;
|
|
88
|
-
source: 'builtin' | 'external' | 'error';
|
|
89
|
-
error?: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Check and cache Nano capability
|
|
93
|
-
export async function getNanoCapability(): Promise<'readily' | 'after-download' | 'no'> {
|
|
94
|
-
// Check session cache first (avoid repeated API calls)
|
|
95
|
-
const cached = await chrome.storage.session.get('nanoCapability');
|
|
96
|
-
if (cached['nanoCapability']) return cached['nanoCapability'] as 'readily' | 'after-download' | 'no';
|
|
97
|
-
|
|
98
|
-
const caps = await chrome.aiLanguageModel.capabilities();
|
|
99
|
-
await chrome.storage.session.set({ nanoCapability: caps.available });
|
|
100
|
-
return caps.available;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Tier 1: Gemini Nano (built-in, on-device)
|
|
104
|
-
async function summarizeWithNano(text: string): Promise<string> {
|
|
105
|
-
const capability = await getNanoCapability();
|
|
106
|
-
|
|
107
|
-
if (capability === 'no') {
|
|
108
|
-
throw new Error('NotSupportedError: Built-in AI not available on this device');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (capability === 'after-download') {
|
|
112
|
-
// Notify UI that model is downloading — caller can show progress
|
|
113
|
-
// Download starts automatically when create() is called
|
|
114
|
-
chrome.runtime.sendMessage({ type: 'AI_DOWNLOADING' });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const session = await chrome.aiLanguageModel.create({
|
|
118
|
-
systemPrompt: 'You are a concise summarizer. Summarize the provided text in 3-5 sentences.',
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const summary = await session.prompt(
|
|
123
|
-
`Summarize this text:\n\n${text.slice(0, 4000)}` // context window limit
|
|
124
|
-
);
|
|
125
|
-
return summary;
|
|
126
|
-
} finally {
|
|
127
|
-
session.destroy(); // always destroy to free resources
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Tier 2: External API (OpenAI-compatible)
|
|
132
|
-
async function summarizeWithExternalApi(text: string): Promise<string> {
|
|
133
|
-
const settings = await storageGet('settings');
|
|
134
|
-
if (!settings.externalApiKey) {
|
|
135
|
-
throw new Error('No external API key configured');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const controller = new AbortController();
|
|
139
|
-
const timeoutId = setTimeout(() => controller.abort(), 30_000);
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
143
|
-
method: 'POST',
|
|
144
|
-
headers: {
|
|
145
|
-
'Content-Type': 'application/json',
|
|
146
|
-
Authorization: `Bearer ${settings.externalApiKey}`,
|
|
147
|
-
},
|
|
148
|
-
body: JSON.stringify({
|
|
149
|
-
model: 'gpt-4o-mini',
|
|
150
|
-
messages: [
|
|
151
|
-
{ role: 'system', content: 'Summarize the provided text in 3-5 sentences.' },
|
|
152
|
-
{ role: 'user', content: text.slice(0, 8000) },
|
|
153
|
-
],
|
|
154
|
-
max_tokens: 300,
|
|
155
|
-
}),
|
|
156
|
-
signal: controller.signal,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (!response.ok) {
|
|
160
|
-
if (response.status === 429) throw new Error('RateLimitError');
|
|
161
|
-
throw new Error(`API error: ${response.status}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const data = await response.json() as {
|
|
165
|
-
choices: Array<{ message: { content: string } }>;
|
|
166
|
-
};
|
|
167
|
-
return data.choices[0]?.message.content ?? '';
|
|
168
|
-
} finally {
|
|
169
|
-
clearTimeout(timeoutId);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Orchestrator — tries each tier in order
|
|
174
|
-
export async function summarize(text: string): Promise<AiSummaryResult> {
|
|
175
|
-
const settings = await storageGet('settings');
|
|
176
|
-
|
|
177
|
-
if (settings.useBuiltinAI) {
|
|
178
|
-
try {
|
|
179
|
-
const summary = await summarizeWithNano(text);
|
|
180
|
-
return { summary, source: 'builtin' };
|
|
181
|
-
} catch (err) {
|
|
182
|
-
console.warn('[AI] Nano failed, falling back to external API:', err);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (settings.externalApiKey) {
|
|
187
|
-
try {
|
|
188
|
-
const summary = await summarizeWithExternalApi(text);
|
|
189
|
-
return { summary, source: 'external' };
|
|
190
|
-
} catch (err) {
|
|
191
|
-
console.error('[AI] External API failed:', err);
|
|
192
|
-
return {
|
|
193
|
-
summary: '',
|
|
194
|
-
source: 'error',
|
|
195
|
-
error: err instanceof Error ? err.message : 'Unknown error',
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
summary: '',
|
|
202
|
-
source: 'error',
|
|
203
|
-
error: 'No AI source available. Enable built-in AI or configure an external API key in Settings.',
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
// Streaming with port (service worker side)
|
|
210
|
-
// background.ts
|
|
211
|
-
chrome.runtime.onConnect.addListener((port) => {
|
|
212
|
-
if (port.name !== 'ai-stream') return;
|
|
213
|
-
|
|
214
|
-
let session: chrome.aiLanguageModel.LanguageModel | null = null;
|
|
215
|
-
|
|
216
|
-
port.onMessage.addListener(async (message: { text: string }) => {
|
|
217
|
-
try {
|
|
218
|
-
const capability = await getNanoCapability();
|
|
219
|
-
if (capability === 'no') throw new Error('NotSupportedError');
|
|
220
|
-
|
|
221
|
-
session = await chrome.aiLanguageModel.create({
|
|
222
|
-
systemPrompt: 'Summarize concisely.',
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
const stream = session.promptStreaming(
|
|
226
|
-
`Summarize:\n\n${message.text.slice(0, 4000)}`
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
let previous = '';
|
|
230
|
-
for await (const chunk of stream) {
|
|
231
|
-
// Chrome's streaming returns cumulative text — extract the delta
|
|
232
|
-
const delta = chunk.slice(previous.length);
|
|
233
|
-
previous = chunk;
|
|
234
|
-
port.postMessage({ type: 'CHUNK', content: delta });
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
port.postMessage({ type: 'DONE' });
|
|
238
|
-
} catch (err) {
|
|
239
|
-
port.postMessage({ type: 'ERROR', error: String(err) });
|
|
240
|
-
} finally {
|
|
241
|
-
session?.destroy();
|
|
242
|
-
session = null;
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
port.onDisconnect.addListener(() => {
|
|
247
|
-
session?.destroy();
|
|
248
|
-
session = null;
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: "ext-ai-integration"
|
|
3
|
+
pack: "@rune/chrome-ext"
|
|
4
|
+
description: "Chrome built-in AI and external API integration — detect AI type, check hardware requirements, implement Gemini Nano with graceful fallback, wire streaming responses via ports, handle rate limits, and test offline behavior."
|
|
5
|
+
model: sonnet
|
|
6
|
+
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ext-ai-integration
|
|
10
|
+
|
|
11
|
+
Chrome built-in AI and external API integration — detect AI type, check hardware requirements, implement Gemini Nano with graceful fallback, wire streaming responses via ports, handle rate limits, and test offline behavior. The differentiating skill for next-generation extensions.
|
|
12
|
+
|
|
13
|
+
**Chrome AI APIs (Chrome 138+ stable):**
|
|
14
|
+
| API | Namespace | Purpose |
|
|
15
|
+
|-----|-----------|---------|
|
|
16
|
+
| Prompt API | `chrome.aiLanguageModel` | General text generation, Q&A, classification |
|
|
17
|
+
| Summarizer | `chrome.aiSummarizer` | Condense long text |
|
|
18
|
+
| Writer | `chrome.aiWriter` | Generate new content from prompts |
|
|
19
|
+
| Rewriter | `chrome.aiRewriter` | Transform existing text (tone, length, format) |
|
|
20
|
+
| Translator | `chrome.aiTranslator` | Language translation |
|
|
21
|
+
| Language Detector | `chrome.aiLanguageDetector` | Detect text language |
|
|
22
|
+
|
|
23
|
+
**Hardware requirements for Gemini Nano:**
|
|
24
|
+
- Storage: 22 GB free disk space (model download)
|
|
25
|
+
- RAM: 4 GB VRAM (dedicated GPU) OR 16 GB system RAM (CPU inference)
|
|
26
|
+
- OS: macOS 13+, Windows 10/11 64-bit, ChromeOS (no Linux support)
|
|
27
|
+
- Cannot be checked programmatically — use capability API and handle `NotSupportedError`
|
|
28
|
+
|
|
29
|
+
**Manifest permission:**
|
|
30
|
+
```json
|
|
31
|
+
{ "permissions": ["aiLanguageModelParams"] }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Workflow
|
|
35
|
+
|
|
36
|
+
**Step 1 — Detect AI integration type**
|
|
37
|
+
Use `Read` on existing source and `manifest.json` to determine:
|
|
38
|
+
- Does `"aiLanguageModelParams"` appear in permissions? → Built-in Nano intended
|
|
39
|
+
- Does code reference `openai`, `anthropic`, `fetch` to an external AI endpoint? → External API
|
|
40
|
+
- Neither? → Need to design integration from scratch
|
|
41
|
+
|
|
42
|
+
Ask the user: "Do you want to use Chrome's built-in Gemini Nano (no API cost, runs on device, requires Chrome 138+ and compatible hardware), an external API (OpenAI/Anthropic, requires API key and network), or both with automatic fallback?"
|
|
43
|
+
|
|
44
|
+
**Step 2 — Check hardware capability for Nano**
|
|
45
|
+
`chrome.aiLanguageModel.capabilities()` returns `{ available: 'readily' | 'after-download' | 'no' }`. Map these:
|
|
46
|
+
- `'readily'` → model is downloaded, use immediately
|
|
47
|
+
- `'after-download'` → model needs download (~2GB), show progress UI and wait
|
|
48
|
+
- `'no'` → hardware not supported, fall through to fallback
|
|
49
|
+
|
|
50
|
+
This check MUST happen in the service worker (not content script — restricted APIs). Cache the result in `chrome.storage.session` to avoid repeated capability checks.
|
|
51
|
+
|
|
52
|
+
**Step 3 — Implement with graceful fallback chain**
|
|
53
|
+
Fallback chain: Gemini Nano → External API → Static response
|
|
54
|
+
|
|
55
|
+
Each tier is a distinct function with the same signature. The orchestrator tries each in order, catching `NotSupportedError`, network errors, and quota errors.
|
|
56
|
+
|
|
57
|
+
**Step 4 — Wire streaming responses via port messaging**
|
|
58
|
+
AI streaming MUST use ports — not `sendMessage`. `sendMessage` is one-shot: the response is sent once and the channel closes. Streaming requires a port to send multiple `CHUNK` messages followed by a `DONE` message.
|
|
59
|
+
|
|
60
|
+
See `ext-messaging` skill for port setup. Streaming pattern:
|
|
61
|
+
1. Sidebar/popup opens a port named `'ai-stream'`
|
|
62
|
+
2. Sends `{ text: inputText }` to start generation
|
|
63
|
+
3. Service worker receives, calls `session.promptStreaming()`
|
|
64
|
+
4. For each chunk in the async iterator, posts `{ type: 'CHUNK', content: chunk }` back on the port
|
|
65
|
+
5. On completion, posts `{ type: 'DONE' }` and calls `session.destroy()`
|
|
66
|
+
|
|
67
|
+
**Step 5 — Handle rate limits and quota**
|
|
68
|
+
Chrome built-in AI has per-session token limits. External APIs have rate limits and cost.
|
|
69
|
+
- Per session: call `session.destroy()` after each summary to free context window
|
|
70
|
+
- External API: implement exponential backoff on 429 responses (1s, 2s, 4s, cap 30s)
|
|
71
|
+
- User-facing: show token usage in settings panel if using external API
|
|
72
|
+
|
|
73
|
+
**Step 6 — Test offline behavior**
|
|
74
|
+
Extensions may run without network. Test:
|
|
75
|
+
- Built-in Nano: works offline (on-device model)
|
|
76
|
+
- External API: fails offline — catch `TypeError: Failed to fetch` and show "No network connection" message
|
|
77
|
+
- Storage: `chrome.storage.local` works offline
|
|
78
|
+
- Service worker: registers and responds to messages offline
|
|
79
|
+
|
|
80
|
+
#### Example
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// src/lib/ai.ts — AI integration with graceful fallback
|
|
84
|
+
import { storageGet } from './storage';
|
|
85
|
+
|
|
86
|
+
export interface AiSummaryResult {
|
|
87
|
+
summary: string;
|
|
88
|
+
source: 'builtin' | 'external' | 'error';
|
|
89
|
+
error?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check and cache Nano capability
|
|
93
|
+
export async function getNanoCapability(): Promise<'readily' | 'after-download' | 'no'> {
|
|
94
|
+
// Check session cache first (avoid repeated API calls)
|
|
95
|
+
const cached = await chrome.storage.session.get('nanoCapability');
|
|
96
|
+
if (cached['nanoCapability']) return cached['nanoCapability'] as 'readily' | 'after-download' | 'no';
|
|
97
|
+
|
|
98
|
+
const caps = await chrome.aiLanguageModel.capabilities();
|
|
99
|
+
await chrome.storage.session.set({ nanoCapability: caps.available });
|
|
100
|
+
return caps.available;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Tier 1: Gemini Nano (built-in, on-device)
|
|
104
|
+
async function summarizeWithNano(text: string): Promise<string> {
|
|
105
|
+
const capability = await getNanoCapability();
|
|
106
|
+
|
|
107
|
+
if (capability === 'no') {
|
|
108
|
+
throw new Error('NotSupportedError: Built-in AI not available on this device');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (capability === 'after-download') {
|
|
112
|
+
// Notify UI that model is downloading — caller can show progress
|
|
113
|
+
// Download starts automatically when create() is called
|
|
114
|
+
chrome.runtime.sendMessage({ type: 'AI_DOWNLOADING' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const session = await chrome.aiLanguageModel.create({
|
|
118
|
+
systemPrompt: 'You are a concise summarizer. Summarize the provided text in 3-5 sentences.',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const summary = await session.prompt(
|
|
123
|
+
`Summarize this text:\n\n${text.slice(0, 4000)}` // context window limit
|
|
124
|
+
);
|
|
125
|
+
return summary;
|
|
126
|
+
} finally {
|
|
127
|
+
session.destroy(); // always destroy to free resources
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Tier 2: External API (OpenAI-compatible)
|
|
132
|
+
async function summarizeWithExternalApi(text: string): Promise<string> {
|
|
133
|
+
const settings = await storageGet('settings');
|
|
134
|
+
if (!settings.externalApiKey) {
|
|
135
|
+
throw new Error('No external API key configured');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const controller = new AbortController();
|
|
139
|
+
const timeoutId = setTimeout(() => controller.abort(), 30_000);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
Authorization: `Bearer ${settings.externalApiKey}`,
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
model: 'gpt-4o-mini',
|
|
150
|
+
messages: [
|
|
151
|
+
{ role: 'system', content: 'Summarize the provided text in 3-5 sentences.' },
|
|
152
|
+
{ role: 'user', content: text.slice(0, 8000) },
|
|
153
|
+
],
|
|
154
|
+
max_tokens: 300,
|
|
155
|
+
}),
|
|
156
|
+
signal: controller.signal,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
if (response.status === 429) throw new Error('RateLimitError');
|
|
161
|
+
throw new Error(`API error: ${response.status}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const data = await response.json() as {
|
|
165
|
+
choices: Array<{ message: { content: string } }>;
|
|
166
|
+
};
|
|
167
|
+
return data.choices[0]?.message.content ?? '';
|
|
168
|
+
} finally {
|
|
169
|
+
clearTimeout(timeoutId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Orchestrator — tries each tier in order
|
|
174
|
+
export async function summarize(text: string): Promise<AiSummaryResult> {
|
|
175
|
+
const settings = await storageGet('settings');
|
|
176
|
+
|
|
177
|
+
if (settings.useBuiltinAI) {
|
|
178
|
+
try {
|
|
179
|
+
const summary = await summarizeWithNano(text);
|
|
180
|
+
return { summary, source: 'builtin' };
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.warn('[AI] Nano failed, falling back to external API:', err);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (settings.externalApiKey) {
|
|
187
|
+
try {
|
|
188
|
+
const summary = await summarizeWithExternalApi(text);
|
|
189
|
+
return { summary, source: 'external' };
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.error('[AI] External API failed:', err);
|
|
192
|
+
return {
|
|
193
|
+
summary: '',
|
|
194
|
+
source: 'error',
|
|
195
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
summary: '',
|
|
202
|
+
source: 'error',
|
|
203
|
+
error: 'No AI source available. Enable built-in AI or configure an external API key in Settings.',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Streaming with port (service worker side)
|
|
210
|
+
// background.ts
|
|
211
|
+
chrome.runtime.onConnect.addListener((port) => {
|
|
212
|
+
if (port.name !== 'ai-stream') return;
|
|
213
|
+
|
|
214
|
+
let session: chrome.aiLanguageModel.LanguageModel | null = null;
|
|
215
|
+
|
|
216
|
+
port.onMessage.addListener(async (message: { text: string }) => {
|
|
217
|
+
try {
|
|
218
|
+
const capability = await getNanoCapability();
|
|
219
|
+
if (capability === 'no') throw new Error('NotSupportedError');
|
|
220
|
+
|
|
221
|
+
session = await chrome.aiLanguageModel.create({
|
|
222
|
+
systemPrompt: 'Summarize concisely.',
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const stream = session.promptStreaming(
|
|
226
|
+
`Summarize:\n\n${message.text.slice(0, 4000)}`
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
let previous = '';
|
|
230
|
+
for await (const chunk of stream) {
|
|
231
|
+
// Chrome's streaming returns cumulative text — extract the delta
|
|
232
|
+
const delta = chunk.slice(previous.length);
|
|
233
|
+
previous = chunk;
|
|
234
|
+
port.postMessage({ type: 'CHUNK', content: delta });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
port.postMessage({ type: 'DONE' });
|
|
238
|
+
} catch (err) {
|
|
239
|
+
port.postMessage({ type: 'ERROR', error: String(err) });
|
|
240
|
+
} finally {
|
|
241
|
+
session?.destroy();
|
|
242
|
+
session = null;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
port.onDisconnect.addListener(() => {
|
|
247
|
+
session?.destroy();
|
|
248
|
+
session = null;
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
```
|