@antseed/provider-claude-oauth 0.1.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/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @antseed/provider-claude-oauth
2
+
3
+ Third-party Anthropic Claude provider plugin with OAuth authentication for Antseed.
4
+
5
+ This plugin demonstrates how a third-party developer can build, test, and publish an Antseed provider plugin using `@antseed/provider-core`.
6
+
7
+ ## How to Create a Plugin
8
+
9
+ 1. Create a new directory under `plugins/` (or anywhere on disk).
10
+ 2. Add a `package.json` with `@antseed/provider-core` as a dependency and `@antseed/node` as a peer dependency.
11
+ 3. Implement and default-export an `AntseedProviderPlugin` object from `src/index.ts`.
12
+ 4. Build with `tsc` and test with `vitest`.
13
+
14
+ ## Plugin Manifest Format
15
+
16
+ Every provider plugin must default-export an object satisfying `AntseedProviderPlugin`:
17
+
18
+ ```typescript
19
+ import type { AntseedProviderPlugin, ConfigField } from '@antseed/node';
20
+
21
+ const plugin: AntseedProviderPlugin = {
22
+ name: 'my-provider', // unique plugin name
23
+ displayName: 'My Provider', // human-readable label
24
+ version: '0.1.0',
25
+ type: 'provider',
26
+ description: 'Description of the provider',
27
+ configSchema: [ // fields the user fills in
28
+ { key: 'API_KEY', label: 'API Key', type: 'secret', required: true },
29
+ ],
30
+ createProvider(config) {
31
+ // Return a Provider instance
32
+ },
33
+ };
34
+ export default plugin;
35
+ ```
36
+
37
+ ### ConfigField Types
38
+
39
+ | Type | Description |
40
+ | ---------- | ---------------------------------- |
41
+ | `string` | Plain text input |
42
+ | `number` | Numeric input |
43
+ | `boolean` | Toggle / checkbox |
44
+ | `secret` | Masked input (tokens, keys) |
45
+ | `string[]` | Comma-separated list of strings |
46
+
47
+ ## Using provider-core
48
+
49
+ The `@antseed/provider-core` package provides reusable building blocks:
50
+
51
+ - **`BaseProvider`** -- Implements the `Provider` interface and wires up `HttpRelay`.
52
+ - **`StaticTokenProvider`** -- Wraps a static API key with no refresh logic.
53
+ - **`OAuthTokenProvider`** -- Manages OAuth access/refresh token pairs with automatic renewal.
54
+ - **`HttpRelay`** -- Forwards requests to an upstream API with auth header swapping, model validation, and concurrency control.
55
+
56
+ ```typescript
57
+ import { BaseProvider, OAuthTokenProvider, StaticTokenProvider } from '@antseed/provider-core';
58
+ ```
59
+
60
+ ## Testing Locally
61
+
62
+ ```bash
63
+ # Install dependencies
64
+ npm install
65
+
66
+ # Build the plugin
67
+ npm run build
68
+
69
+ # Run tests
70
+ npm test
71
+ ```
72
+
73
+ To test the plugin end-to-end with a local Antseed node, link it:
74
+
75
+ ```bash
76
+ cd plugins/provider-claude-oauth
77
+ npm link
78
+
79
+ cd /path/to/your/antseed-project
80
+ npm link @anthropic/provider-claude-oauth
81
+ ```
82
+
83
+ Then configure the plugin in your node's plugin config with the required config fields.
84
+
85
+ ## Publishing to npm
86
+
87
+ 1. Ensure `package.json` has the correct `name`, `version`, and `description`.
88
+ 2. Build the plugin: `npm run build`
89
+ 3. Login to npm: `npm login`
90
+ 4. Publish: `npm publish --access public`
91
+
92
+ For scoped packages like `@anthropic/provider-claude-oauth`, use `--access public` to publish as a public package.
93
+
94
+ ## Installing via antseed plugin add
95
+
96
+ Once published, users can install the plugin:
97
+
98
+ ```bash
99
+ antseed plugin add @anthropic/provider-claude-oauth
100
+ ```
101
+
102
+ This will download the plugin from npm, register it with the local Antseed node, and prompt the user to configure the required fields (access token, etc.).
103
+
104
+ ## Configuration Reference
105
+
106
+ | Key | Type | Required | Default | Description |
107
+ | ------------------------------ | -------- | -------- | ------- | ------------------------------------ |
108
+ | `CLAUDE_ACCESS_TOKEN` | secret | Yes | -- | Claude OAuth access token |
109
+ | `CLAUDE_REFRESH_TOKEN` | secret | No | -- | OAuth refresh token for auto-renewal |
110
+ | `CLAUDE_TOKEN_EXPIRES_AT` | number | No | -- | Epoch ms when access token expires |
111
+ | `ANTSEED_INPUT_USD_PER_MILLION`| number | No | 10 | Input token price (USD per 1M) |
112
+ | `ANTSEED_OUTPUT_USD_PER_MILLION`| number | No | 10 | Output token price (USD per 1M) |
113
+ | `ANTSEED_MAX_CONCURRENCY` | number | No | 5 | Max concurrent requests |
114
+ | `ANTSEED_ALLOWED_MODELS` | string[] | No | -- | Comma-separated list of model IDs |
115
+
116
+ ## License
117
+
118
+ See the root repository LICENSE file.
@@ -0,0 +1,4 @@
1
+ import type { AntseedProviderPlugin } from '@antseed/node';
2
+ declare const plugin: AntseedProviderPlugin;
3
+ export default plugin;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAe,MAAM,eAAe,CAAC;AAaxE,QAAA,MAAM,MAAM,EAAE,qBAoDb,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ import { BaseProvider, OAuthTokenProvider, StaticTokenProvider } from '@antseed/provider-core';
2
+ const configSchema = [
3
+ { key: 'CLAUDE_ACCESS_TOKEN', label: 'Access Token', type: 'secret', required: true, description: 'Claude OAuth access token' },
4
+ { key: 'CLAUDE_REFRESH_TOKEN', label: 'Refresh Token', type: 'secret', required: false, description: 'OAuth refresh token for auto-renewal' },
5
+ { key: 'CLAUDE_TOKEN_EXPIRES_AT', label: 'Token Expiry', type: 'number', required: false, description: 'Epoch ms when access token expires' },
6
+ { key: 'ANTSEED_INPUT_USD_PER_MILLION', label: 'Input Price', type: 'number', required: false, default: 10 },
7
+ { key: 'ANTSEED_OUTPUT_USD_PER_MILLION', label: 'Output Price', type: 'number', required: false, default: 10 },
8
+ { key: 'ANTSEED_MAX_CONCURRENCY', label: 'Max Concurrency', type: 'number', required: false, default: 5 },
9
+ { key: 'ANTSEED_ALLOWED_MODELS', label: 'Allowed Models', type: 'string[]', required: false },
10
+ ];
11
+ const plugin = {
12
+ name: 'claude-oauth',
13
+ displayName: 'Claude (OAuth)',
14
+ version: '0.1.0',
15
+ type: 'provider',
16
+ description: 'Anthropic Claude with OAuth authentication (third-party example)',
17
+ configSchema,
18
+ configKeys: configSchema,
19
+ createProvider(config) {
20
+ const accessToken = config['CLAUDE_ACCESS_TOKEN'];
21
+ if (!accessToken)
22
+ throw new Error('CLAUDE_ACCESS_TOKEN is required');
23
+ const refreshToken = config['CLAUDE_REFRESH_TOKEN'];
24
+ const expiresAt = config['CLAUDE_TOKEN_EXPIRES_AT']
25
+ ? parseInt(config['CLAUDE_TOKEN_EXPIRES_AT'], 10)
26
+ : undefined;
27
+ const tokenProvider = refreshToken
28
+ ? new OAuthTokenProvider({
29
+ accessToken,
30
+ refreshToken,
31
+ expiresAt: expiresAt ?? Date.now() + 3600_000,
32
+ tokenEndpoint: 'https://console.anthropic.com/v1/oauth/token',
33
+ })
34
+ : new StaticTokenProvider(accessToken);
35
+ const inputPrice = parseFloat(config['ANTSEED_INPUT_USD_PER_MILLION'] ?? '10');
36
+ const outputPrice = parseFloat(config['ANTSEED_OUTPUT_USD_PER_MILLION'] ?? '10');
37
+ const maxConcurrency = parseInt(config['ANTSEED_MAX_CONCURRENCY'] ?? '5', 10);
38
+ const allowedModels = (config['ANTSEED_ALLOWED_MODELS'] ?? '')
39
+ .split(',').map(s => s.trim()).filter(Boolean);
40
+ return new BaseProvider({
41
+ name: 'claude-oauth',
42
+ models: allowedModels,
43
+ pricing: {
44
+ defaults: {
45
+ inputUsdPerMillion: inputPrice,
46
+ outputUsdPerMillion: outputPrice,
47
+ },
48
+ },
49
+ relay: {
50
+ baseUrl: 'https://api.anthropic.com',
51
+ authHeaderName: 'authorization',
52
+ authHeaderValue: `Bearer ${accessToken}`,
53
+ tokenProvider,
54
+ extraHeaders: { 'anthropic-beta': 'oauth-2025-04-20' },
55
+ maxConcurrency,
56
+ allowedModels,
57
+ },
58
+ });
59
+ },
60
+ };
61
+ export default plugin;
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@antseed/provider-claude-oauth",
3
+ "version": "0.1.0",
4
+ "description": "Third-party Anthropic Claude provider with OAuth authentication",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "prebuild": "rm -rf dist",
10
+ "build": "tsc",
11
+ "test": "vitest run",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@antseed/provider-core": "workspace:*"
16
+ },
17
+ "peerDependencies": {
18
+ "@antseed/node": ">=0.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.5.0",
22
+ "vitest": "^2.0.0",
23
+ "@antseed/node": "workspace:*"
24
+ }
25
+ }
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import plugin from './index.js';
3
+
4
+ describe('provider-claude-oauth plugin manifest', () => {
5
+ it('has correct plugin metadata', () => {
6
+ expect(plugin.name).toBe('claude-oauth');
7
+ expect(plugin.displayName).toBe('Claude (OAuth)');
8
+ expect(plugin.version).toBe('0.1.0');
9
+ expect(plugin.type).toBe('provider');
10
+ expect(plugin.description).toBe('Anthropic Claude with OAuth authentication (third-party example)');
11
+ });
12
+
13
+ it('exposes configSchema with required fields', () => {
14
+ expect(plugin.configSchema).toBeDefined();
15
+ const keys = plugin.configSchema!.map(f => f.key);
16
+ expect(keys).toContain('CLAUDE_ACCESS_TOKEN');
17
+ expect(keys).toContain('CLAUDE_REFRESH_TOKEN');
18
+ expect(keys).toContain('CLAUDE_TOKEN_EXPIRES_AT');
19
+ expect(keys).toContain('ANTSEED_INPUT_USD_PER_MILLION');
20
+ expect(keys).toContain('ANTSEED_OUTPUT_USD_PER_MILLION');
21
+ expect(keys).toContain('ANTSEED_MAX_CONCURRENCY');
22
+ expect(keys).toContain('ANTSEED_ALLOWED_MODELS');
23
+ const accessField = plugin.configSchema!.find(f => f.key === 'CLAUDE_ACCESS_TOKEN');
24
+ expect(accessField!.required).toBe(true);
25
+ expect(accessField!.type).toBe('secret');
26
+ });
27
+ });
28
+
29
+ describe('createProvider', () => {
30
+ it('creates provider with access token only (static)', () => {
31
+ const provider = plugin.createProvider({
32
+ CLAUDE_ACCESS_TOKEN: 'test-access-token',
33
+ });
34
+ expect(provider).toBeDefined();
35
+ expect(provider.name).toBe('claude-oauth');
36
+ expect(provider.maxConcurrency).toBe(5);
37
+ });
38
+
39
+ it('creates provider with access + refresh token (OAuth)', () => {
40
+ const provider = plugin.createProvider({
41
+ CLAUDE_ACCESS_TOKEN: 'test-access-token',
42
+ CLAUDE_REFRESH_TOKEN: 'test-refresh-token',
43
+ CLAUDE_TOKEN_EXPIRES_AT: String(Date.now() + 3600_000),
44
+ });
45
+ expect(provider).toBeDefined();
46
+ expect(provider.name).toBe('claude-oauth');
47
+ });
48
+
49
+ it('rejects missing access token', () => {
50
+ expect(() => plugin.createProvider({})).toThrow('CLAUDE_ACCESS_TOKEN is required');
51
+ });
52
+
53
+ it('provider has correct name and pricing', () => {
54
+ const provider = plugin.createProvider({
55
+ CLAUDE_ACCESS_TOKEN: 'test-access-token',
56
+ ANTSEED_INPUT_USD_PER_MILLION: '15',
57
+ ANTSEED_OUTPUT_USD_PER_MILLION: '30',
58
+ ANTSEED_MAX_CONCURRENCY: '3',
59
+ });
60
+ expect(provider.name).toBe('claude-oauth');
61
+ expect(provider.pricing).toEqual({
62
+ defaults: {
63
+ inputUsdPerMillion: 15,
64
+ outputUsdPerMillion: 30,
65
+ },
66
+ });
67
+ expect(provider.maxConcurrency).toBe(3);
68
+ });
69
+ });
package/src/index.ts ADDED
@@ -0,0 +1,68 @@
1
+ import type { AntseedProviderPlugin, ConfigField } from '@antseed/node';
2
+ import { BaseProvider, OAuthTokenProvider, StaticTokenProvider } from '@antseed/provider-core';
3
+
4
+ const configSchema: ConfigField[] = [
5
+ { key: 'CLAUDE_ACCESS_TOKEN', label: 'Access Token', type: 'secret', required: true, description: 'Claude OAuth access token' },
6
+ { key: 'CLAUDE_REFRESH_TOKEN', label: 'Refresh Token', type: 'secret', required: false, description: 'OAuth refresh token for auto-renewal' },
7
+ { key: 'CLAUDE_TOKEN_EXPIRES_AT', label: 'Token Expiry', type: 'number', required: false, description: 'Epoch ms when access token expires' },
8
+ { key: 'ANTSEED_INPUT_USD_PER_MILLION', label: 'Input Price', type: 'number', required: false, default: 10 },
9
+ { key: 'ANTSEED_OUTPUT_USD_PER_MILLION', label: 'Output Price', type: 'number', required: false, default: 10 },
10
+ { key: 'ANTSEED_MAX_CONCURRENCY', label: 'Max Concurrency', type: 'number', required: false, default: 5 },
11
+ { key: 'ANTSEED_ALLOWED_MODELS', label: 'Allowed Models', type: 'string[]', required: false },
12
+ ];
13
+
14
+ const plugin: AntseedProviderPlugin = {
15
+ name: 'claude-oauth',
16
+ displayName: 'Claude (OAuth)',
17
+ version: '0.1.0',
18
+ type: 'provider',
19
+ description: 'Anthropic Claude with OAuth authentication (third-party example)',
20
+ configSchema,
21
+ configKeys: configSchema,
22
+ createProvider(config: Record<string, string>) {
23
+ const accessToken = config['CLAUDE_ACCESS_TOKEN'];
24
+ if (!accessToken) throw new Error('CLAUDE_ACCESS_TOKEN is required');
25
+
26
+ const refreshToken = config['CLAUDE_REFRESH_TOKEN'];
27
+ const expiresAt = config['CLAUDE_TOKEN_EXPIRES_AT']
28
+ ? parseInt(config['CLAUDE_TOKEN_EXPIRES_AT'], 10)
29
+ : undefined;
30
+
31
+ const tokenProvider = refreshToken
32
+ ? new OAuthTokenProvider({
33
+ accessToken,
34
+ refreshToken,
35
+ expiresAt: expiresAt ?? Date.now() + 3600_000,
36
+ tokenEndpoint: 'https://console.anthropic.com/v1/oauth/token',
37
+ })
38
+ : new StaticTokenProvider(accessToken);
39
+
40
+ const inputPrice = parseFloat(config['ANTSEED_INPUT_USD_PER_MILLION'] ?? '10');
41
+ const outputPrice = parseFloat(config['ANTSEED_OUTPUT_USD_PER_MILLION'] ?? '10');
42
+ const maxConcurrency = parseInt(config['ANTSEED_MAX_CONCURRENCY'] ?? '5', 10);
43
+ const allowedModels = (config['ANTSEED_ALLOWED_MODELS'] ?? '')
44
+ .split(',').map(s => s.trim()).filter(Boolean);
45
+
46
+ return new BaseProvider({
47
+ name: 'claude-oauth',
48
+ models: allowedModels,
49
+ pricing: {
50
+ defaults: {
51
+ inputUsdPerMillion: inputPrice,
52
+ outputUsdPerMillion: outputPrice,
53
+ },
54
+ },
55
+ relay: {
56
+ baseUrl: 'https://api.anthropic.com',
57
+ authHeaderName: 'authorization',
58
+ authHeaderValue: `Bearer ${accessToken}`,
59
+ tokenProvider,
60
+ extraHeaders: { 'anthropic-beta': 'oauth-2025-04-20' },
61
+ maxConcurrency,
62
+ allowedModels,
63
+ },
64
+ });
65
+ },
66
+ };
67
+
68
+ export default plugin;
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["src/**/*.test.ts"]
9
+ }