resources
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# Resource Configuration Guide
|
||||
|
||||
You are an AI assistant helping users configure **resources** — external services and APIs that Officer connects to.
|
||||
|
||||
## What is a Resource?
|
||||
|
||||
A resource represents an external service (e.g., a TTS server, an STT API, an OCR endpoint). Each resource has:
|
||||
- A `RESOURCE.md` file with frontmatter metadata (name, description) and a markdown body
|
||||
- A `config.json` file with flat key-value connection settings (all strings)
|
||||
|
||||
## config.json Format
|
||||
|
||||
The config is a flat JSON object where every value is a string. Empty string means "not set".
|
||||
|
||||
Base fields (always present):
|
||||
- `url` — the service endpoint URL
|
||||
- `api_key` — API key or token for authentication
|
||||
- `username` — username for basic auth
|
||||
- `password` — password for basic auth
|
||||
|
||||
Additional fields vary per resource (e.g., `provider`, `model`, `voice`).
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"url": "http://localhost:8000",
|
||||
"api_key": "",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"provider": "openai",
|
||||
"model": "tts-1",
|
||||
"voice": "alloy"
|
||||
}
|
||||
```
|
||||
|
||||
## How to Help the User
|
||||
|
||||
1. **Ask what service they're connecting to** — provider name, URL, auth method
|
||||
2. **Fill in the config.json** — write the file with the values they provide
|
||||
3. **Explain each field** — tell them what each key does and what format it expects
|
||||
4. **For new resources**, also write the `RESOURCE.md` with an appropriate name and description in the frontmatter
|
||||
|
||||
## RESOURCE.md Format
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Human Readable Name
|
||||
description: One-line description of what this resource does
|
||||
---
|
||||
Optional longer markdown body with usage notes, compatible providers, etc.
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
# Optical Character Recognition — OCR API
|
||||
|
||||
- **Type:** OCR
|
||||
- **Port:** 8082
|
||||
- **Description:** HTTP server for optical character recognition. Compatible with OpenAI-style vision APIs. Configure the URL and model in the connection settings below.
|
||||
- **Verify:** `curl -sf {url}/v1/models`
|
||||
@@ -0,0 +1,6 @@
|
||||
# Speech to Text — Transcription API
|
||||
|
||||
- **Type:** STT
|
||||
- **Port:** 8080
|
||||
- **Description:** HTTP server for speech-to-text transcription. Compatible with the whisper.cpp server API. Configure the URL in the connection settings below.
|
||||
- **Verify:** `curl -sf {url}/health`
|
||||
@@ -0,0 +1,6 @@
|
||||
# Text to Speech — Synthesis API
|
||||
|
||||
- **Type:** TTS
|
||||
- **Port:** 8000
|
||||
- **Description:** HTTP server for text-to-speech synthesis. Compatible with the OpenAI audio/speech API (mlx-audio, Kokoro, etc.) and ElevenLabs. Configure the URL and credentials in the connection settings below.
|
||||
- **Verify:** `curl -sf {url}/v1/models`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
name: Optical Character Recognition
|
||||
description: HTTP server for optical character recognition
|
||||
---
|
||||
Compatible with OpenAI-style vision APIs for extracting text from images.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"url": "",
|
||||
"api_key": "",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"model": ""
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
name: Speech to Text
|
||||
description: HTTP server for speech-to-text transcription
|
||||
---
|
||||
Compatible with the whisper.cpp server API and OpenAI-compatible transcription endpoints.
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"url": "",
|
||||
"api_key": "",
|
||||
"username": "",
|
||||
"password": ""
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
name: Text to Speech
|
||||
description: HTTP server for text-to-speech synthesis
|
||||
---
|
||||
Compatible with OpenAI audio/speech API (mlx-audio, Kokoro, etc.) and ElevenLabs.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"url": "",
|
||||
"api_key": "",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"provider": "",
|
||||
"model": "",
|
||||
"voice": ""
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: ocr
|
||||
label: OCR
|
||||
description: Extract text from an image file using the configured OCR service. Sends the image to the OCR API (OpenAI-compatible vision endpoint) and returns the extracted text. Use this tool whenever you need to read text from images, screenshots, documents, receipts, etc. Requires OCR to be configured in Settings → Resources.
|
||||
language: typescript
|
||||
inputs:
|
||||
file_path:
|
||||
type: string
|
||||
description: Absolute path to the image file to extract text from
|
||||
prompt:
|
||||
type: string
|
||||
description: Optional instructions for the OCR model (e.g. "extract only the table" or "return as markdown")
|
||||
optional: true
|
||||
---
|
||||
|
||||
# OCR Tool
|
||||
|
||||
Extracts text from images using the configured OCR resource (OpenAI-compatible vision API).
|
||||
|
||||
## Supported formats
|
||||
|
||||
PNG, JPEG, WebP, GIF, and other common image formats.
|
||||
|
||||
## Output
|
||||
|
||||
Returns the extracted text content. For documents, preserves structure as markdown.
|
||||
For tables, uses markdown table format. For code screenshots, uses fenced code blocks.
|
||||
@@ -0,0 +1,112 @@
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { extname } from 'node:path';
|
||||
|
||||
type OcrConfig = {
|
||||
url: string;
|
||||
model: string;
|
||||
api_key?: string;
|
||||
};
|
||||
|
||||
function getOcrConfig(): OcrConfig | null {
|
||||
try {
|
||||
const raw = process.env.OFFICER_RESOURCES;
|
||||
if (!raw) return null;
|
||||
const resources = JSON.parse(raw) as Record<string, Record<string, string>>;
|
||||
const ocr = resources['optical-character-recognition'];
|
||||
if (!ocr?.url) return null;
|
||||
return { url: ocr.url, model: ocr.model ?? '', api_key: ocr.api_key };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_PROMPT = [
|
||||
'You are an OCR assistant. Extract meaningful text content from images.',
|
||||
'Rules:',
|
||||
'- Output ONLY the extracted text, no commentary or explanations.',
|
||||
'- For documents, articles, books: preserve the original text, paragraphs, and structure as markdown.',
|
||||
'- For tables: use markdown table format.',
|
||||
'- For code/terminal screenshots: use fenced code blocks.',
|
||||
'- For handwritten text: do your best to transcribe accurately.',
|
||||
'- For mixed content: use appropriate formatting for each section.',
|
||||
].join('\n');
|
||||
|
||||
export async function execute(
|
||||
_toolCallId: string,
|
||||
params: { file_path: string; prompt?: string },
|
||||
) {
|
||||
const config = getOcrConfig();
|
||||
if (!config) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'OCR is not configured. Set it up in Settings → Resources → Optical Character Recognition.' }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const { file_path, prompt } = params;
|
||||
|
||||
if (!existsSync(file_path)) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `File not found: ${file_path}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const imageBytes = readFileSync(file_path);
|
||||
const base64 = imageBytes.toString('base64');
|
||||
const ext = extname(file_path).replace('.', '').toLowerCase();
|
||||
const mime = ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg' : ext === 'webp' ? 'image/webp' : ext === 'gif' ? 'image/gif' : `image/${ext || 'png'}`;
|
||||
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (config.api_key) headers['Authorization'] = `Bearer ${config.api_key}`;
|
||||
|
||||
const systemPrompt = prompt ? `${DEFAULT_PROMPT}\n\nAdditional instructions: ${prompt}` : DEFAULT_PROMPT;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${config.url.replace(/\/+$/, '')}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'image_url', image_url: { url: `data:${mime};base64,${base64}` } },
|
||||
],
|
||||
},
|
||||
],
|
||||
max_tokens: 4096,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text().catch(() => '');
|
||||
return {
|
||||
content: [{ type: 'text', text: `OCR API error (${res.status}): ${errorText}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const json = await res.json() as { choices?: Array<{ message?: { content?: string } }> };
|
||||
const text = json.choices?.[0]?.message?.content ?? '';
|
||||
|
||||
if (!text) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'OCR returned empty result — the image may not contain readable text.' }],
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return {
|
||||
content: [{ type: 'text', text: `OCR request failed: ${message}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user