@tinyclaw/plugin-provider-openai 1.0.1-dev.3004a38

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/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @tinyclaw/plugin-provider-openai
2
+
3
+ OpenAI provider plugin for Tiny Claw. Adds OpenAI as an LLM provider, enabling the agent to use GPT models.
4
+
5
+ ## Setup
6
+
7
+ 1. Obtain an API key from <https://platform.openai.com/api-keys>
8
+ 2. Ask the agent to pair the OpenAI provider
9
+ 3. Provide the API key when prompted
10
+
11
+ ## How It Works
12
+
13
+ - Registers an OpenAI-compatible provider with the agent
14
+ - API key is stored securely via the secrets engine
15
+ - Supports model selection through the agent's model management tools
16
+
17
+ ## License
18
+
19
+ GPLv3
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @tinyclaw/plugin-provider-openai
3
+ *
4
+ * OpenAI provider plugin for Tiny Claw. Adds GPT-4.1, GPT-4o, and other
5
+ * OpenAI models as a provider option. Routes via the smart provider
6
+ * routing system based on query complexity tiers.
7
+ *
8
+ * Pairing flow:
9
+ * 1. Plugin is added to `plugins.enabled` (manually or via set_config)
10
+ * 2. On next boot, pairing tools (`openai_pair`, `openai_unpair`) appear
11
+ * 3. User provides API key via `openai_pair` → stored securely, tier mapping updated
12
+ * 4. Agent calls `tinyclaw_restart` → supervisor respawns with new configuration
13
+ */
14
+ import type { ProviderPlugin } from '@tinyclaw/types';
15
+ declare const openaiPlugin: ProviderPlugin;
16
+ export default openaiPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @tinyclaw/plugin-provider-openai
3
+ *
4
+ * OpenAI provider plugin for Tiny Claw. Adds GPT-4.1, GPT-4o, and other
5
+ * OpenAI models as a provider option. Routes via the smart provider
6
+ * routing system based on query complexity tiers.
7
+ *
8
+ * Pairing flow:
9
+ * 1. Plugin is added to `plugins.enabled` (manually or via set_config)
10
+ * 2. On next boot, pairing tools (`openai_pair`, `openai_unpair`) appear
11
+ * 3. User provides API key via `openai_pair` → stored securely, tier mapping updated
12
+ * 4. Agent calls `tinyclaw_restart` → supervisor respawns with new configuration
13
+ */
14
+ import { createOpenAIProvider } from './provider.js';
15
+ import { createOpenAIPairingTools } from './pairing.js';
16
+ const openaiPlugin = {
17
+ id: '@tinyclaw/plugin-provider-openai',
18
+ name: 'OpenAI',
19
+ description: 'OpenAI GPT models (GPT-4.1, GPT-4o, etc.)',
20
+ type: 'provider',
21
+ version: '0.1.0',
22
+ async createProvider(secrets) {
23
+ return createOpenAIProvider({ secrets });
24
+ },
25
+ getPairingTools(secrets, configManager) {
26
+ return createOpenAIPairingTools(secrets, configManager);
27
+ },
28
+ };
29
+ export default openaiPlugin;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * OpenAI Pairing Tools
3
+ *
4
+ * Two tools that implement the OpenAI provider pairing flow:
5
+ *
6
+ * 1. openai_pair — Store the API key, configure model, enable plugin, update tier mapping
7
+ * 2. openai_unpair — Disable plugin and reset tier mapping
8
+ *
9
+ * These tools are injected into the agent's tool list at boot so the agent
10
+ * can invoke them conversationally when a user asks to connect OpenAI.
11
+ */
12
+ import type { Tool, SecretsManagerInterface, ConfigManagerInterface } from '@tinyclaw/types';
13
+ /** Secret key for the OpenAI API key. */
14
+ export declare const OPENAI_SECRET_KEY = "provider.openai.apiKey";
15
+ /** Config key for the model setting. */
16
+ export declare const OPENAI_MODEL_CONFIG_KEY = "providers.openai.model";
17
+ /** The plugin's package ID. */
18
+ export declare const OPENAI_PLUGIN_ID = "@tinyclaw/plugin-provider-openai";
19
+ /** The provider ID used in tier mapping. */
20
+ export declare const OPENAI_PROVIDER_ID = "openai";
21
+ /** Default model. */
22
+ export declare const OPENAI_DEFAULT_MODEL = "gpt-4.1";
23
+ export declare function createOpenAIPairingTools(secrets: SecretsManagerInterface, configManager: ConfigManagerInterface): Tool[];
@@ -0,0 +1,110 @@
1
+ /**
2
+ * OpenAI Pairing Tools
3
+ *
4
+ * Two tools that implement the OpenAI provider pairing flow:
5
+ *
6
+ * 1. openai_pair — Store the API key, configure model, enable plugin, update tier mapping
7
+ * 2. openai_unpair — Disable plugin and reset tier mapping
8
+ *
9
+ * These tools are injected into the agent's tool list at boot so the agent
10
+ * can invoke them conversationally when a user asks to connect OpenAI.
11
+ */
12
+ /** Secret key for the OpenAI API key. */
13
+ export const OPENAI_SECRET_KEY = 'provider.openai.apiKey';
14
+ /** Config key for the model setting. */
15
+ export const OPENAI_MODEL_CONFIG_KEY = 'providers.openai.model';
16
+ /** The plugin's package ID. */
17
+ export const OPENAI_PLUGIN_ID = '@tinyclaw/plugin-provider-openai';
18
+ /** The provider ID used in tier mapping. */
19
+ export const OPENAI_PROVIDER_ID = 'openai';
20
+ /** Default model. */
21
+ export const OPENAI_DEFAULT_MODEL = 'gpt-4.1';
22
+ export function createOpenAIPairingTools(secrets, configManager) {
23
+ return [
24
+ {
25
+ name: 'openai_pair',
26
+ description: 'Pair Tiny Claw with OpenAI as a provider. ' +
27
+ 'Stores the API key securely, configures the model, enables the plugin, ' +
28
+ 'and routes complex/reasoning queries to OpenAI. ' +
29
+ 'After pairing, call tinyclaw_restart to apply the changes. ' +
30
+ 'To get an API key: go to https://platform.openai.com/api-keys and create one.',
31
+ parameters: {
32
+ type: 'object',
33
+ properties: {
34
+ apiKey: {
35
+ type: 'string',
36
+ description: 'OpenAI API key (starts with sk-)',
37
+ },
38
+ model: {
39
+ type: 'string',
40
+ description: 'OpenAI model to use (default: gpt-4.1). ' +
41
+ 'Examples: gpt-4.1, gpt-4o, gpt-4.1-mini',
42
+ },
43
+ },
44
+ required: ['apiKey'],
45
+ },
46
+ async execute(args) {
47
+ const apiKey = args.apiKey;
48
+ if (!apiKey || apiKey.trim() === '') {
49
+ return 'Error: apiKey must be a non-empty string.';
50
+ }
51
+ const model = args.model?.trim() || OPENAI_DEFAULT_MODEL;
52
+ try {
53
+ // 1. Store API key in secrets engine
54
+ await secrets.store(OPENAI_SECRET_KEY, apiKey.trim());
55
+ // 2. Set model in config
56
+ configManager.set(OPENAI_MODEL_CONFIG_KEY, model);
57
+ // 3. Add plugin to enabled list (deduplicated)
58
+ const current = configManager.get('plugins.enabled') ?? [];
59
+ if (!current.includes(OPENAI_PLUGIN_ID)) {
60
+ configManager.set('plugins.enabled', [...current, OPENAI_PLUGIN_ID]);
61
+ }
62
+ // 4. Update tier mapping — route complex + reasoning to OpenAI
63
+ configManager.set('routing.tierMapping.complex', OPENAI_PROVIDER_ID);
64
+ configManager.set('routing.tierMapping.reasoning', OPENAI_PROVIDER_ID);
65
+ return (`OpenAI provider paired successfully! ` +
66
+ `Model: ${model}. API key stored securely. ` +
67
+ `Complex and reasoning queries will be routed to OpenAI. ` +
68
+ `Use the tinyclaw_restart tool now to apply the changes.`);
69
+ }
70
+ catch (err) {
71
+ return `Error pairing OpenAI: ${err.message}`;
72
+ }
73
+ },
74
+ },
75
+ {
76
+ name: 'openai_unpair',
77
+ description: 'Disconnect OpenAI provider and disable the plugin. ' +
78
+ 'Resets routing so all queries go back to the default provider. ' +
79
+ 'The API key is kept in secrets for safety. Call tinyclaw_restart after.',
80
+ parameters: {
81
+ type: 'object',
82
+ properties: {},
83
+ required: [],
84
+ },
85
+ async execute() {
86
+ try {
87
+ // 1. Remove from plugins.enabled
88
+ const current = configManager.get('plugins.enabled') ?? [];
89
+ configManager.set('plugins.enabled', current.filter((id) => id !== OPENAI_PLUGIN_ID));
90
+ // 2. Reset tier mapping entries that point to 'openai'
91
+ const tiers = ['simple', 'moderate', 'complex', 'reasoning'];
92
+ for (const tier of tiers) {
93
+ const key = `routing.tierMapping.${tier}`;
94
+ const val = configManager.get(key);
95
+ if (val === OPENAI_PROVIDER_ID) {
96
+ configManager.set(key, 'ollama-cloud');
97
+ }
98
+ }
99
+ return ('OpenAI provider disabled. ' +
100
+ 'All queries will route to the default provider. ' +
101
+ 'Use the tinyclaw_restart tool now to apply the changes. ' +
102
+ 'The API key is still stored in secrets — use list_secrets to manage it.');
103
+ }
104
+ catch (err) {
105
+ return `Error unpairing OpenAI: ${err.message}`;
106
+ }
107
+ },
108
+ },
109
+ ];
110
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * OpenAI Provider
3
+ *
4
+ * Creates a Provider that talks to the OpenAI Chat Completions API.
5
+ * Uses fetch directly — no external SDK dependency.
6
+ *
7
+ * Supports:
8
+ * - Configurable model (default: gpt-4.1)
9
+ * - Configurable base URL (for Azure / OpenAI-compatible endpoints)
10
+ * - Tool/function calling with automatic format conversion
11
+ * - API key resolution from secrets-engine
12
+ */
13
+ import type { Provider, SecretsManagerInterface } from '@tinyclaw/types';
14
+ export interface OpenAIProviderConfig {
15
+ secrets: SecretsManagerInterface;
16
+ /** Model to use (default: 'gpt-4.1'). */
17
+ model?: string;
18
+ /** Base URL for the API (default: 'https://api.openai.com'). */
19
+ baseUrl?: string;
20
+ }
21
+ export declare function createOpenAIProvider(config: OpenAIProviderConfig): Provider;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * OpenAI Provider
3
+ *
4
+ * Creates a Provider that talks to the OpenAI Chat Completions API.
5
+ * Uses fetch directly — no external SDK dependency.
6
+ *
7
+ * Supports:
8
+ * - Configurable model (default: gpt-4.1)
9
+ * - Configurable base URL (for Azure / OpenAI-compatible endpoints)
10
+ * - Tool/function calling with automatic format conversion
11
+ * - API key resolution from secrets-engine
12
+ */
13
+ import { logger } from '@tinyclaw/logger';
14
+ function toOpenAIMessages(messages) {
15
+ return messages.map((msg) => {
16
+ const out = {
17
+ role: msg.role,
18
+ content: msg.content ?? null,
19
+ };
20
+ // Assistant messages with tool calls
21
+ if (msg.role === 'assistant' && msg.toolCalls?.length) {
22
+ out.tool_calls = msg.toolCalls.map((tc) => ({
23
+ id: tc.id,
24
+ type: 'function',
25
+ function: {
26
+ name: tc.name,
27
+ arguments: JSON.stringify(tc.arguments),
28
+ },
29
+ }));
30
+ }
31
+ // Tool result messages
32
+ if (msg.role === 'tool' && msg.toolCallId) {
33
+ out.tool_call_id = msg.toolCallId;
34
+ }
35
+ return out;
36
+ });
37
+ }
38
+ function toOpenAITools(tools) {
39
+ return tools.map((t) => ({
40
+ type: 'function',
41
+ function: {
42
+ name: t.name,
43
+ description: t.description,
44
+ parameters: t.parameters,
45
+ },
46
+ }));
47
+ }
48
+ function parseToolCalls(raw) {
49
+ return raw.map((tc) => ({
50
+ id: tc.id,
51
+ name: tc.function.name,
52
+ arguments: JSON.parse(tc.function.arguments),
53
+ }));
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Provider factory
57
+ // ---------------------------------------------------------------------------
58
+ export function createOpenAIProvider(config) {
59
+ const baseUrl = config.baseUrl || 'https://api.openai.com';
60
+ const model = config.model || 'gpt-4.1';
61
+ return {
62
+ id: 'openai',
63
+ name: `OpenAI (${model})`,
64
+ async chat(messages, tools) {
65
+ try {
66
+ const apiKey = await config.secrets.resolveProviderKey('openai');
67
+ if (!apiKey) {
68
+ throw new Error('No API key available for OpenAI. ' +
69
+ 'Store one with: store_secret key="provider.openai.apiKey" value="sk-..."');
70
+ }
71
+ const body = {
72
+ model,
73
+ messages: toOpenAIMessages(messages),
74
+ };
75
+ if (tools?.length) {
76
+ body.tools = toOpenAITools(tools);
77
+ }
78
+ const response = await fetch(`${baseUrl}/v1/chat/completions`, {
79
+ method: 'POST',
80
+ headers: {
81
+ 'Authorization': `Bearer ${apiKey}`,
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ body: JSON.stringify(body),
85
+ });
86
+ if (!response.ok) {
87
+ const errorBody = await response.text();
88
+ throw new Error(`OpenAI API error: ${response.status} ${response.statusText} — ${errorBody}`);
89
+ }
90
+ const data = await response.json();
91
+ logger.debug('OpenAI raw response:', JSON.stringify(data).slice(0, 500));
92
+ const choice = data.choices?.[0]?.message;
93
+ if (!choice) {
94
+ throw new Error('OpenAI API returned no choices');
95
+ }
96
+ // Tool calls response
97
+ if (choice.tool_calls?.length) {
98
+ return {
99
+ type: 'tool_calls',
100
+ content: choice.content ?? undefined,
101
+ toolCalls: parseToolCalls(choice.tool_calls),
102
+ };
103
+ }
104
+ // Text response
105
+ return {
106
+ type: 'text',
107
+ content: choice.content ?? '',
108
+ };
109
+ }
110
+ catch (error) {
111
+ logger.error('OpenAI provider error:', error);
112
+ throw error;
113
+ }
114
+ },
115
+ async isAvailable() {
116
+ try {
117
+ const apiKey = await config.secrets.resolveProviderKey('openai');
118
+ if (!apiKey)
119
+ return false;
120
+ const response = await fetch(`${baseUrl}/v1/models`, {
121
+ headers: {
122
+ 'Authorization': `Bearer ${apiKey}`,
123
+ },
124
+ });
125
+ return response.ok;
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ },
131
+ };
132
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@tinyclaw/plugin-provider-openai",
3
+ "version": "1.0.1-dev.3004a38",
4
+ "description": "OpenAI provider plugin for Tiny Claw",
5
+ "license": "GPL-3.0",
6
+ "author": "Waren Gonzaga",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": "./dist/index.js"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/warengonzaga/tinyclaw.git",
19
+ "directory": "plugins/provider/plugin-provider-openai"
20
+ },
21
+ "homepage": "https://github.com/warengonzaga/tinyclaw/tree/main/plugins/provider/plugin-provider-openai#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/warengonzaga/tinyclaw/issues"
24
+ },
25
+ "keywords": [
26
+ "tinyclaw",
27
+ "plugin",
28
+ "provider",
29
+ "openai"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc -p tsconfig.json"
33
+ },
34
+ "dependencies": {
35
+ "@tinyclaw/logger": "workspace:*",
36
+ "@tinyclaw/types": "workspace:*"
37
+ }
38
+ }