polymorph-sdk 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 +102 -0
- package/dist/index.d.mts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +264 -0
- package/dist/index.mjs +233 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Polymorph SDK for TypeScript
|
|
2
|
+
|
|
3
|
+
Tool logging wrapper for Vercel AI SDK. Automatically logs tool calls to the Polymorph API for monitoring and analytics.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install polymorph-sdk ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { streamText, tool } from "ai";
|
|
15
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { PolymorphConfig, createPolymorphAIClient } from "polymorph-sdk";
|
|
18
|
+
|
|
19
|
+
// Create Polymorph config
|
|
20
|
+
const config = new PolymorphConfig({
|
|
21
|
+
role: "user",
|
|
22
|
+
apiKey: "your-api-key", // or set POLYMORPH_API_KEY env var
|
|
23
|
+
baseUrl: "https://api.polymorph.dev", // or set POLYMORPH_BASE_URL env var
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Create Polymorph client
|
|
27
|
+
const polymorph = createPolymorphAIClient({ config });
|
|
28
|
+
|
|
29
|
+
// Define your tools
|
|
30
|
+
const tools = {
|
|
31
|
+
getWeather: tool({
|
|
32
|
+
description: "Get the weather for a location",
|
|
33
|
+
parameters: z.object({
|
|
34
|
+
location: z.string().describe("The location to get weather for"),
|
|
35
|
+
}),
|
|
36
|
+
execute: async ({ location }) => {
|
|
37
|
+
// Your tool implementation
|
|
38
|
+
return { temperature: 72, condition: "sunny" };
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Wrap tools with Polymorph logging
|
|
44
|
+
const wrappedTools = polymorph.wrapTools(tools);
|
|
45
|
+
|
|
46
|
+
// Use with streamText
|
|
47
|
+
const result = await streamText({
|
|
48
|
+
model: anthropic("claude-sonnet-4-20250514"),
|
|
49
|
+
tools: wrappedTools,
|
|
50
|
+
messages: [{ role: "user", content: "What's the weather in San Francisco?" }],
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Alternative: Wrap streamText/generateText
|
|
55
|
+
|
|
56
|
+
You can also wrap the AI SDK functions directly:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { streamText, generateText } from "ai";
|
|
60
|
+
import { PolymorphConfig, createPolymorphAIClient } from "polymorph-sdk";
|
|
61
|
+
|
|
62
|
+
const config = new PolymorphConfig({ role: "user" });
|
|
63
|
+
const polymorph = createPolymorphAIClient({ config });
|
|
64
|
+
|
|
65
|
+
// Create wrapped versions of streamText and generateText
|
|
66
|
+
const polymorphStreamText = polymorph.streamText(streamText);
|
|
67
|
+
const polymorphGenerateText = polymorph.generateText(generateText);
|
|
68
|
+
|
|
69
|
+
// Now use these wrapped functions - tools will be automatically logged
|
|
70
|
+
const result = await polymorphStreamText({
|
|
71
|
+
model: anthropic("claude-sonnet-4-20250514"),
|
|
72
|
+
tools: myTools, // Tools are automatically wrapped
|
|
73
|
+
messages: [...],
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
|--------|------|---------|-------------|
|
|
81
|
+
| `role` | `string` | (required) | User role for RBAC functionality |
|
|
82
|
+
| `apiKey` | `string` | `POLYMORPH_API_KEY` env var | API key for authentication |
|
|
83
|
+
| `baseUrl` | `string` | `POLYMORPH_BASE_URL` env var | Base URL for Polymorph API |
|
|
84
|
+
| `httpTimeout` | `number` | `10000` | HTTP request timeout in milliseconds |
|
|
85
|
+
| `httpEnabled` | `boolean` | `true` | Whether HTTP logging is enabled |
|
|
86
|
+
|
|
87
|
+
## Environment Variables
|
|
88
|
+
|
|
89
|
+
- `POLYMORPH_API_KEY` - API key for authentication
|
|
90
|
+
- `POLYMORPH_BASE_URL` - Base URL for Polymorph API
|
|
91
|
+
|
|
92
|
+
## API Endpoints
|
|
93
|
+
|
|
94
|
+
The SDK sends events to the following endpoints:
|
|
95
|
+
|
|
96
|
+
- `POST /sdk/init` - Called on client initialization
|
|
97
|
+
- `POST /sdk/tool/pre` - Called before tool execution
|
|
98
|
+
- `POST /sdk/tool/post` - Called after tool execution
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ToolSet, streamText, generateText } from 'ai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for Polymorph SDK.
|
|
5
|
+
*/
|
|
6
|
+
interface PolymorphConfigOptions {
|
|
7
|
+
/**
|
|
8
|
+
* User role for potential RBAC functionality.
|
|
9
|
+
*/
|
|
10
|
+
role: string;
|
|
11
|
+
/**
|
|
12
|
+
* API key for authentication. Can also be set via POLYMORPH_API_KEY env var.
|
|
13
|
+
*/
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Base URL for the Polymorph API. Can also be set via POLYMORPH_BASE_URL env var.
|
|
17
|
+
*/
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* HTTP request timeout in milliseconds. Defaults to 10000 (10 seconds).
|
|
21
|
+
*/
|
|
22
|
+
httpTimeout?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Whether HTTP logging is enabled. Defaults to true.
|
|
25
|
+
*/
|
|
26
|
+
httpEnabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for tool logging and permissions.
|
|
30
|
+
*/
|
|
31
|
+
declare class PolymorphConfig {
|
|
32
|
+
readonly role: string;
|
|
33
|
+
readonly apiKey: string | undefined;
|
|
34
|
+
readonly baseUrl: string | undefined;
|
|
35
|
+
readonly httpTimeout: number;
|
|
36
|
+
readonly httpEnabled: boolean;
|
|
37
|
+
constructor(options: PolymorphConfigOptions);
|
|
38
|
+
private getEnv;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* HTTP client for Polymorph API calls.
|
|
43
|
+
*
|
|
44
|
+
* All errors are caught and logged - never crashes the SDK.
|
|
45
|
+
*/
|
|
46
|
+
declare class PolymorphHTTPClient {
|
|
47
|
+
private baseUrl;
|
|
48
|
+
private apiKey;
|
|
49
|
+
private timeout;
|
|
50
|
+
private enabled;
|
|
51
|
+
constructor(options: {
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
apiKey?: string;
|
|
54
|
+
timeout?: number;
|
|
55
|
+
enabled?: boolean;
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Make a POST request to the given endpoint.
|
|
59
|
+
*
|
|
60
|
+
* Returns the response JSON if successful, undefined otherwise.
|
|
61
|
+
* All errors are caught and logged.
|
|
62
|
+
*/
|
|
63
|
+
post<T = unknown>(endpoint: string, payload: Record<string, unknown>): Promise<T | undefined>;
|
|
64
|
+
/**
|
|
65
|
+
* Fire-and-forget POST request. Does not wait for response.
|
|
66
|
+
*/
|
|
67
|
+
postFireAndForget(endpoint: string, payload: Record<string, unknown>): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Log an entry to configured destinations.
|
|
72
|
+
*/
|
|
73
|
+
declare function log(entry: Record<string, unknown>): void;
|
|
74
|
+
|
|
75
|
+
type StreamTextParams = Parameters<typeof streamText>[0];
|
|
76
|
+
type GenerateTextParams = Parameters<typeof generateText>[0];
|
|
77
|
+
type StreamTextResult = ReturnType<typeof streamText>;
|
|
78
|
+
type GenerateTextResult = ReturnType<typeof generateText>;
|
|
79
|
+
/**
|
|
80
|
+
* Hook context passed to pre/post tool hooks.
|
|
81
|
+
*/
|
|
82
|
+
interface ToolHookContext {
|
|
83
|
+
toolName: string;
|
|
84
|
+
toolCallId: string;
|
|
85
|
+
input: unknown;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Pre-tool hook result. Return `{ deny: true, reason: string }` to deny the tool call.
|
|
89
|
+
*/
|
|
90
|
+
interface PreToolHookResult {
|
|
91
|
+
deny?: boolean;
|
|
92
|
+
reason?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Post-tool hook context with the result.
|
|
96
|
+
*/
|
|
97
|
+
interface PostToolHookContext extends ToolHookContext {
|
|
98
|
+
result: unknown;
|
|
99
|
+
error?: Error;
|
|
100
|
+
durationMs: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Options for creating a Polymorph AI client.
|
|
104
|
+
*/
|
|
105
|
+
interface PolymorphAIClientOptions {
|
|
106
|
+
config: PolymorphConfig;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Polymorph AI client that wraps Vercel AI SDK functions with tool logging.
|
|
110
|
+
*/
|
|
111
|
+
declare class PolymorphAIClient {
|
|
112
|
+
private config;
|
|
113
|
+
private httpClient;
|
|
114
|
+
private preToolHook;
|
|
115
|
+
private postToolHook;
|
|
116
|
+
constructor(options: PolymorphAIClientOptions);
|
|
117
|
+
/**
|
|
118
|
+
* Wraps tools with Polymorph logging hooks.
|
|
119
|
+
*/
|
|
120
|
+
wrapTools<T extends ToolSet>(tools: T): T;
|
|
121
|
+
/**
|
|
122
|
+
* Creates a wrapped streamText function that automatically wraps tools.
|
|
123
|
+
*/
|
|
124
|
+
streamText(originalStreamText: typeof streamText): (params: StreamTextParams) => StreamTextResult;
|
|
125
|
+
/**
|
|
126
|
+
* Creates a wrapped generateText function that automatically wraps tools.
|
|
127
|
+
*/
|
|
128
|
+
generateText(originalGenerateText: typeof generateText): (params: GenerateTextParams) => GenerateTextResult;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Creates a Polymorph AI client for wrapping Vercel AI SDK functions.
|
|
132
|
+
*/
|
|
133
|
+
declare function createPolymorphAIClient(options: PolymorphAIClientOptions): PolymorphAIClient;
|
|
134
|
+
|
|
135
|
+
export { PolymorphAIClient, type PolymorphAIClientOptions, PolymorphConfig, type PolymorphConfigOptions, PolymorphHTTPClient, type PostToolHookContext, type PreToolHookResult, type ToolHookContext, createPolymorphAIClient, log };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ToolSet, streamText, generateText } from 'ai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for Polymorph SDK.
|
|
5
|
+
*/
|
|
6
|
+
interface PolymorphConfigOptions {
|
|
7
|
+
/**
|
|
8
|
+
* User role for potential RBAC functionality.
|
|
9
|
+
*/
|
|
10
|
+
role: string;
|
|
11
|
+
/**
|
|
12
|
+
* API key for authentication. Can also be set via POLYMORPH_API_KEY env var.
|
|
13
|
+
*/
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Base URL for the Polymorph API. Can also be set via POLYMORPH_BASE_URL env var.
|
|
17
|
+
*/
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* HTTP request timeout in milliseconds. Defaults to 10000 (10 seconds).
|
|
21
|
+
*/
|
|
22
|
+
httpTimeout?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Whether HTTP logging is enabled. Defaults to true.
|
|
25
|
+
*/
|
|
26
|
+
httpEnabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for tool logging and permissions.
|
|
30
|
+
*/
|
|
31
|
+
declare class PolymorphConfig {
|
|
32
|
+
readonly role: string;
|
|
33
|
+
readonly apiKey: string | undefined;
|
|
34
|
+
readonly baseUrl: string | undefined;
|
|
35
|
+
readonly httpTimeout: number;
|
|
36
|
+
readonly httpEnabled: boolean;
|
|
37
|
+
constructor(options: PolymorphConfigOptions);
|
|
38
|
+
private getEnv;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* HTTP client for Polymorph API calls.
|
|
43
|
+
*
|
|
44
|
+
* All errors are caught and logged - never crashes the SDK.
|
|
45
|
+
*/
|
|
46
|
+
declare class PolymorphHTTPClient {
|
|
47
|
+
private baseUrl;
|
|
48
|
+
private apiKey;
|
|
49
|
+
private timeout;
|
|
50
|
+
private enabled;
|
|
51
|
+
constructor(options: {
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
apiKey?: string;
|
|
54
|
+
timeout?: number;
|
|
55
|
+
enabled?: boolean;
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Make a POST request to the given endpoint.
|
|
59
|
+
*
|
|
60
|
+
* Returns the response JSON if successful, undefined otherwise.
|
|
61
|
+
* All errors are caught and logged.
|
|
62
|
+
*/
|
|
63
|
+
post<T = unknown>(endpoint: string, payload: Record<string, unknown>): Promise<T | undefined>;
|
|
64
|
+
/**
|
|
65
|
+
* Fire-and-forget POST request. Does not wait for response.
|
|
66
|
+
*/
|
|
67
|
+
postFireAndForget(endpoint: string, payload: Record<string, unknown>): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Log an entry to configured destinations.
|
|
72
|
+
*/
|
|
73
|
+
declare function log(entry: Record<string, unknown>): void;
|
|
74
|
+
|
|
75
|
+
type StreamTextParams = Parameters<typeof streamText>[0];
|
|
76
|
+
type GenerateTextParams = Parameters<typeof generateText>[0];
|
|
77
|
+
type StreamTextResult = ReturnType<typeof streamText>;
|
|
78
|
+
type GenerateTextResult = ReturnType<typeof generateText>;
|
|
79
|
+
/**
|
|
80
|
+
* Hook context passed to pre/post tool hooks.
|
|
81
|
+
*/
|
|
82
|
+
interface ToolHookContext {
|
|
83
|
+
toolName: string;
|
|
84
|
+
toolCallId: string;
|
|
85
|
+
input: unknown;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Pre-tool hook result. Return `{ deny: true, reason: string }` to deny the tool call.
|
|
89
|
+
*/
|
|
90
|
+
interface PreToolHookResult {
|
|
91
|
+
deny?: boolean;
|
|
92
|
+
reason?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Post-tool hook context with the result.
|
|
96
|
+
*/
|
|
97
|
+
interface PostToolHookContext extends ToolHookContext {
|
|
98
|
+
result: unknown;
|
|
99
|
+
error?: Error;
|
|
100
|
+
durationMs: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Options for creating a Polymorph AI client.
|
|
104
|
+
*/
|
|
105
|
+
interface PolymorphAIClientOptions {
|
|
106
|
+
config: PolymorphConfig;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Polymorph AI client that wraps Vercel AI SDK functions with tool logging.
|
|
110
|
+
*/
|
|
111
|
+
declare class PolymorphAIClient {
|
|
112
|
+
private config;
|
|
113
|
+
private httpClient;
|
|
114
|
+
private preToolHook;
|
|
115
|
+
private postToolHook;
|
|
116
|
+
constructor(options: PolymorphAIClientOptions);
|
|
117
|
+
/**
|
|
118
|
+
* Wraps tools with Polymorph logging hooks.
|
|
119
|
+
*/
|
|
120
|
+
wrapTools<T extends ToolSet>(tools: T): T;
|
|
121
|
+
/**
|
|
122
|
+
* Creates a wrapped streamText function that automatically wraps tools.
|
|
123
|
+
*/
|
|
124
|
+
streamText(originalStreamText: typeof streamText): (params: StreamTextParams) => StreamTextResult;
|
|
125
|
+
/**
|
|
126
|
+
* Creates a wrapped generateText function that automatically wraps tools.
|
|
127
|
+
*/
|
|
128
|
+
generateText(originalGenerateText: typeof generateText): (params: GenerateTextParams) => GenerateTextResult;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Creates a Polymorph AI client for wrapping Vercel AI SDK functions.
|
|
132
|
+
*/
|
|
133
|
+
declare function createPolymorphAIClient(options: PolymorphAIClientOptions): PolymorphAIClient;
|
|
134
|
+
|
|
135
|
+
export { PolymorphAIClient, type PolymorphAIClientOptions, PolymorphConfig, type PolymorphConfigOptions, PolymorphHTTPClient, type PostToolHookContext, type PreToolHookResult, type ToolHookContext, createPolymorphAIClient, log };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
PolymorphAIClient: () => PolymorphAIClient,
|
|
24
|
+
PolymorphConfig: () => PolymorphConfig,
|
|
25
|
+
PolymorphHTTPClient: () => PolymorphHTTPClient,
|
|
26
|
+
createPolymorphAIClient: () => createPolymorphAIClient,
|
|
27
|
+
log: () => log
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/config.ts
|
|
32
|
+
var PolymorphConfig = class {
|
|
33
|
+
constructor(options) {
|
|
34
|
+
this.role = options.role;
|
|
35
|
+
this.apiKey = options.apiKey ?? this.getEnv("POLYMORPH_API_KEY");
|
|
36
|
+
this.baseUrl = options.baseUrl ?? this.getEnv("POLYMORPH_BASE_URL");
|
|
37
|
+
this.httpTimeout = options.httpTimeout ?? 1e4;
|
|
38
|
+
this.httpEnabled = options.httpEnabled ?? true;
|
|
39
|
+
if (!this.apiKey) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"apiKey is required. Provide it in the constructor or set POLYMORPH_API_KEY env var."
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
getEnv(key) {
|
|
46
|
+
if (typeof process !== "undefined" && process.env) {
|
|
47
|
+
return process.env[key];
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/http-client.ts
|
|
54
|
+
var PolymorphHTTPClient = class {
|
|
55
|
+
constructor(options) {
|
|
56
|
+
this.baseUrl = options.baseUrl;
|
|
57
|
+
this.apiKey = options.apiKey;
|
|
58
|
+
this.timeout = options.timeout ?? 1e4;
|
|
59
|
+
this.enabled = options.enabled ?? true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Make a POST request to the given endpoint.
|
|
63
|
+
*
|
|
64
|
+
* Returns the response JSON if successful, undefined otherwise.
|
|
65
|
+
* All errors are caught and logged.
|
|
66
|
+
*/
|
|
67
|
+
async post(endpoint, payload) {
|
|
68
|
+
if (!this.enabled) {
|
|
69
|
+
console.debug(`[Polymorph] HTTP client disabled, skipping POST to ${endpoint}`);
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
if (!this.baseUrl) {
|
|
73
|
+
console.debug(`[Polymorph] No baseUrl configured, skipping POST to ${endpoint}`);
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
const url = `${this.baseUrl.replace(/\/$/, "")}${endpoint}`;
|
|
77
|
+
const headers = {
|
|
78
|
+
"Content-Type": "application/json"
|
|
79
|
+
};
|
|
80
|
+
if (this.apiKey) {
|
|
81
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
82
|
+
}
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
85
|
+
try {
|
|
86
|
+
const response = await fetch(url, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers,
|
|
89
|
+
body: JSON.stringify(payload),
|
|
90
|
+
signal: controller.signal
|
|
91
|
+
});
|
|
92
|
+
clearTimeout(timeoutId);
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
console.warn(
|
|
95
|
+
`[Polymorph] HTTP error ${response.status} while posting to ${url}`
|
|
96
|
+
);
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
return await response.json();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
clearTimeout(timeoutId);
|
|
102
|
+
if (error instanceof Error) {
|
|
103
|
+
if (error.name === "AbortError") {
|
|
104
|
+
console.warn(`[Polymorph] Timeout while posting to ${url}`);
|
|
105
|
+
} else {
|
|
106
|
+
console.warn(`[Polymorph] Request error while posting to ${url}:`, error.message);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
console.warn(`[Polymorph] Unexpected error while posting to ${url}:`, error);
|
|
110
|
+
}
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Fire-and-forget POST request. Does not wait for response.
|
|
116
|
+
*/
|
|
117
|
+
postFireAndForget(endpoint, payload) {
|
|
118
|
+
this.post(endpoint, payload);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/logging.ts
|
|
123
|
+
function log(entry) {
|
|
124
|
+
console.log("[Polymorph]", entry);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/providers/vercel-ai.ts
|
|
128
|
+
function createHooks(config, httpClient) {
|
|
129
|
+
async function preToolHook(context) {
|
|
130
|
+
const payload = {
|
|
131
|
+
event: "tool_call_start",
|
|
132
|
+
tool: context.toolName,
|
|
133
|
+
input: context.input,
|
|
134
|
+
tool_use_id: context.toolCallId,
|
|
135
|
+
role: config.role
|
|
136
|
+
};
|
|
137
|
+
log(payload);
|
|
138
|
+
httpClient.postFireAndForget("/sdk/tool/pre", payload);
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
async function postToolHook(context) {
|
|
142
|
+
const payload = {
|
|
143
|
+
event: "tool_call_end",
|
|
144
|
+
tool: context.toolName,
|
|
145
|
+
tool_use_id: context.toolCallId,
|
|
146
|
+
duration_ms: context.durationMs,
|
|
147
|
+
success: !context.error,
|
|
148
|
+
error: context.error?.message,
|
|
149
|
+
role: config.role
|
|
150
|
+
};
|
|
151
|
+
log(payload);
|
|
152
|
+
httpClient.postFireAndForget("/sdk/tool/post", payload);
|
|
153
|
+
}
|
|
154
|
+
return { preToolHook, postToolHook };
|
|
155
|
+
}
|
|
156
|
+
function wrapTool(toolName, tool, preToolHook, postToolHook) {
|
|
157
|
+
if (!tool.execute) {
|
|
158
|
+
return tool;
|
|
159
|
+
}
|
|
160
|
+
const originalExecute = tool.execute;
|
|
161
|
+
const wrappedExecute = async (input, options) => {
|
|
162
|
+
const toolCallId = options?.toolCallId ?? `call_${Date.now()}`;
|
|
163
|
+
const context = {
|
|
164
|
+
toolName,
|
|
165
|
+
toolCallId,
|
|
166
|
+
input
|
|
167
|
+
};
|
|
168
|
+
const preResult = await preToolHook(context);
|
|
169
|
+
if (preResult.deny) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Tool '${toolName}' denied: ${preResult.reason ?? "Permission denied"}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const startTime = Date.now();
|
|
175
|
+
let result;
|
|
176
|
+
let error;
|
|
177
|
+
try {
|
|
178
|
+
result = await originalExecute(input, options);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
181
|
+
throw error;
|
|
182
|
+
} finally {
|
|
183
|
+
const durationMs = Date.now() - startTime;
|
|
184
|
+
await postToolHook({
|
|
185
|
+
...context,
|
|
186
|
+
result,
|
|
187
|
+
error,
|
|
188
|
+
durationMs
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
...tool,
|
|
195
|
+
execute: wrappedExecute
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function wrapTools(tools, preToolHook, postToolHook) {
|
|
199
|
+
const wrappedTools = {};
|
|
200
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
201
|
+
wrappedTools[name] = wrapTool(name, tool, preToolHook, postToolHook);
|
|
202
|
+
}
|
|
203
|
+
return wrappedTools;
|
|
204
|
+
}
|
|
205
|
+
var PolymorphAIClient = class {
|
|
206
|
+
constructor(options) {
|
|
207
|
+
this.config = options.config;
|
|
208
|
+
this.httpClient = new PolymorphHTTPClient({
|
|
209
|
+
baseUrl: this.config.baseUrl,
|
|
210
|
+
apiKey: this.config.apiKey,
|
|
211
|
+
timeout: this.config.httpTimeout,
|
|
212
|
+
enabled: this.config.httpEnabled
|
|
213
|
+
});
|
|
214
|
+
const hooks = createHooks(this.config, this.httpClient);
|
|
215
|
+
this.preToolHook = hooks.preToolHook;
|
|
216
|
+
this.postToolHook = hooks.postToolHook;
|
|
217
|
+
this.httpClient.postFireAndForget("/sdk/init", {
|
|
218
|
+
role: this.config.role,
|
|
219
|
+
sdk: "vercel-ai",
|
|
220
|
+
version: "0.1.0"
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Wraps tools with Polymorph logging hooks.
|
|
225
|
+
*/
|
|
226
|
+
wrapTools(tools) {
|
|
227
|
+
return wrapTools(tools, this.preToolHook, this.postToolHook);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Creates a wrapped streamText function that automatically wraps tools.
|
|
231
|
+
*/
|
|
232
|
+
streamText(originalStreamText) {
|
|
233
|
+
return (params) => {
|
|
234
|
+
const wrappedParams = {
|
|
235
|
+
...params,
|
|
236
|
+
tools: params.tools ? this.wrapTools(params.tools) : void 0
|
|
237
|
+
};
|
|
238
|
+
return originalStreamText(wrappedParams);
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Creates a wrapped generateText function that automatically wraps tools.
|
|
243
|
+
*/
|
|
244
|
+
generateText(originalGenerateText) {
|
|
245
|
+
return (params) => {
|
|
246
|
+
const wrappedParams = {
|
|
247
|
+
...params,
|
|
248
|
+
tools: params.tools ? this.wrapTools(params.tools) : void 0
|
|
249
|
+
};
|
|
250
|
+
return originalGenerateText(wrappedParams);
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
function createPolymorphAIClient(options) {
|
|
255
|
+
return new PolymorphAIClient(options);
|
|
256
|
+
}
|
|
257
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
258
|
+
0 && (module.exports = {
|
|
259
|
+
PolymorphAIClient,
|
|
260
|
+
PolymorphConfig,
|
|
261
|
+
PolymorphHTTPClient,
|
|
262
|
+
createPolymorphAIClient,
|
|
263
|
+
log
|
|
264
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
var PolymorphConfig = class {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.role = options.role;
|
|
5
|
+
this.apiKey = options.apiKey ?? this.getEnv("POLYMORPH_API_KEY");
|
|
6
|
+
this.baseUrl = options.baseUrl ?? this.getEnv("POLYMORPH_BASE_URL");
|
|
7
|
+
this.httpTimeout = options.httpTimeout ?? 1e4;
|
|
8
|
+
this.httpEnabled = options.httpEnabled ?? true;
|
|
9
|
+
if (!this.apiKey) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"apiKey is required. Provide it in the constructor or set POLYMORPH_API_KEY env var."
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
getEnv(key) {
|
|
16
|
+
if (typeof process !== "undefined" && process.env) {
|
|
17
|
+
return process.env[key];
|
|
18
|
+
}
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/http-client.ts
|
|
24
|
+
var PolymorphHTTPClient = class {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.baseUrl = options.baseUrl;
|
|
27
|
+
this.apiKey = options.apiKey;
|
|
28
|
+
this.timeout = options.timeout ?? 1e4;
|
|
29
|
+
this.enabled = options.enabled ?? true;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Make a POST request to the given endpoint.
|
|
33
|
+
*
|
|
34
|
+
* Returns the response JSON if successful, undefined otherwise.
|
|
35
|
+
* All errors are caught and logged.
|
|
36
|
+
*/
|
|
37
|
+
async post(endpoint, payload) {
|
|
38
|
+
if (!this.enabled) {
|
|
39
|
+
console.debug(`[Polymorph] HTTP client disabled, skipping POST to ${endpoint}`);
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
if (!this.baseUrl) {
|
|
43
|
+
console.debug(`[Polymorph] No baseUrl configured, skipping POST to ${endpoint}`);
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
const url = `${this.baseUrl.replace(/\/$/, "")}${endpoint}`;
|
|
47
|
+
const headers = {
|
|
48
|
+
"Content-Type": "application/json"
|
|
49
|
+
};
|
|
50
|
+
if (this.apiKey) {
|
|
51
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
52
|
+
}
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers,
|
|
59
|
+
body: JSON.stringify(payload),
|
|
60
|
+
signal: controller.signal
|
|
61
|
+
});
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
console.warn(
|
|
65
|
+
`[Polymorph] HTTP error ${response.status} while posting to ${url}`
|
|
66
|
+
);
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
return await response.json();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
clearTimeout(timeoutId);
|
|
72
|
+
if (error instanceof Error) {
|
|
73
|
+
if (error.name === "AbortError") {
|
|
74
|
+
console.warn(`[Polymorph] Timeout while posting to ${url}`);
|
|
75
|
+
} else {
|
|
76
|
+
console.warn(`[Polymorph] Request error while posting to ${url}:`, error.message);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
console.warn(`[Polymorph] Unexpected error while posting to ${url}:`, error);
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fire-and-forget POST request. Does not wait for response.
|
|
86
|
+
*/
|
|
87
|
+
postFireAndForget(endpoint, payload) {
|
|
88
|
+
this.post(endpoint, payload);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/logging.ts
|
|
93
|
+
function log(entry) {
|
|
94
|
+
console.log("[Polymorph]", entry);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/providers/vercel-ai.ts
|
|
98
|
+
function createHooks(config, httpClient) {
|
|
99
|
+
async function preToolHook(context) {
|
|
100
|
+
const payload = {
|
|
101
|
+
event: "tool_call_start",
|
|
102
|
+
tool: context.toolName,
|
|
103
|
+
input: context.input,
|
|
104
|
+
tool_use_id: context.toolCallId,
|
|
105
|
+
role: config.role
|
|
106
|
+
};
|
|
107
|
+
log(payload);
|
|
108
|
+
httpClient.postFireAndForget("/sdk/tool/pre", payload);
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
async function postToolHook(context) {
|
|
112
|
+
const payload = {
|
|
113
|
+
event: "tool_call_end",
|
|
114
|
+
tool: context.toolName,
|
|
115
|
+
tool_use_id: context.toolCallId,
|
|
116
|
+
duration_ms: context.durationMs,
|
|
117
|
+
success: !context.error,
|
|
118
|
+
error: context.error?.message,
|
|
119
|
+
role: config.role
|
|
120
|
+
};
|
|
121
|
+
log(payload);
|
|
122
|
+
httpClient.postFireAndForget("/sdk/tool/post", payload);
|
|
123
|
+
}
|
|
124
|
+
return { preToolHook, postToolHook };
|
|
125
|
+
}
|
|
126
|
+
function wrapTool(toolName, tool, preToolHook, postToolHook) {
|
|
127
|
+
if (!tool.execute) {
|
|
128
|
+
return tool;
|
|
129
|
+
}
|
|
130
|
+
const originalExecute = tool.execute;
|
|
131
|
+
const wrappedExecute = async (input, options) => {
|
|
132
|
+
const toolCallId = options?.toolCallId ?? `call_${Date.now()}`;
|
|
133
|
+
const context = {
|
|
134
|
+
toolName,
|
|
135
|
+
toolCallId,
|
|
136
|
+
input
|
|
137
|
+
};
|
|
138
|
+
const preResult = await preToolHook(context);
|
|
139
|
+
if (preResult.deny) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Tool '${toolName}' denied: ${preResult.reason ?? "Permission denied"}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const startTime = Date.now();
|
|
145
|
+
let result;
|
|
146
|
+
let error;
|
|
147
|
+
try {
|
|
148
|
+
result = await originalExecute(input, options);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
151
|
+
throw error;
|
|
152
|
+
} finally {
|
|
153
|
+
const durationMs = Date.now() - startTime;
|
|
154
|
+
await postToolHook({
|
|
155
|
+
...context,
|
|
156
|
+
result,
|
|
157
|
+
error,
|
|
158
|
+
durationMs
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
...tool,
|
|
165
|
+
execute: wrappedExecute
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function wrapTools(tools, preToolHook, postToolHook) {
|
|
169
|
+
const wrappedTools = {};
|
|
170
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
171
|
+
wrappedTools[name] = wrapTool(name, tool, preToolHook, postToolHook);
|
|
172
|
+
}
|
|
173
|
+
return wrappedTools;
|
|
174
|
+
}
|
|
175
|
+
var PolymorphAIClient = class {
|
|
176
|
+
constructor(options) {
|
|
177
|
+
this.config = options.config;
|
|
178
|
+
this.httpClient = new PolymorphHTTPClient({
|
|
179
|
+
baseUrl: this.config.baseUrl,
|
|
180
|
+
apiKey: this.config.apiKey,
|
|
181
|
+
timeout: this.config.httpTimeout,
|
|
182
|
+
enabled: this.config.httpEnabled
|
|
183
|
+
});
|
|
184
|
+
const hooks = createHooks(this.config, this.httpClient);
|
|
185
|
+
this.preToolHook = hooks.preToolHook;
|
|
186
|
+
this.postToolHook = hooks.postToolHook;
|
|
187
|
+
this.httpClient.postFireAndForget("/sdk/init", {
|
|
188
|
+
role: this.config.role,
|
|
189
|
+
sdk: "vercel-ai",
|
|
190
|
+
version: "0.1.0"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Wraps tools with Polymorph logging hooks.
|
|
195
|
+
*/
|
|
196
|
+
wrapTools(tools) {
|
|
197
|
+
return wrapTools(tools, this.preToolHook, this.postToolHook);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Creates a wrapped streamText function that automatically wraps tools.
|
|
201
|
+
*/
|
|
202
|
+
streamText(originalStreamText) {
|
|
203
|
+
return (params) => {
|
|
204
|
+
const wrappedParams = {
|
|
205
|
+
...params,
|
|
206
|
+
tools: params.tools ? this.wrapTools(params.tools) : void 0
|
|
207
|
+
};
|
|
208
|
+
return originalStreamText(wrappedParams);
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Creates a wrapped generateText function that automatically wraps tools.
|
|
213
|
+
*/
|
|
214
|
+
generateText(originalGenerateText) {
|
|
215
|
+
return (params) => {
|
|
216
|
+
const wrappedParams = {
|
|
217
|
+
...params,
|
|
218
|
+
tools: params.tools ? this.wrapTools(params.tools) : void 0
|
|
219
|
+
};
|
|
220
|
+
return originalGenerateText(wrappedParams);
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
function createPolymorphAIClient(options) {
|
|
225
|
+
return new PolymorphAIClient(options);
|
|
226
|
+
}
|
|
227
|
+
export {
|
|
228
|
+
PolymorphAIClient,
|
|
229
|
+
PolymorphConfig,
|
|
230
|
+
PolymorphHTTPClient,
|
|
231
|
+
createPolymorphAIClient,
|
|
232
|
+
log
|
|
233
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "polymorph-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tool logging wrapper for Vercel AI SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
21
|
+
"lint": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"ai",
|
|
27
|
+
"llm",
|
|
28
|
+
"vercel",
|
|
29
|
+
"ai-sdk",
|
|
30
|
+
"polymorph",
|
|
31
|
+
"tool-logging"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"ai": "^6.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.0.0",
|
|
40
|
+
"tsup": "^8.0.0",
|
|
41
|
+
"typescript": "^5.0.0",
|
|
42
|
+
"vitest": "^2.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"ai": ">=4.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|