@xquik/tweetclaw 1.4.1 → 1.5.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 +2 -2
- package/openclaw.plugin.json +5 -5
- package/package.json +10 -2
- package/skills/tweetclaw/SKILL.md +2 -2
- package/src/index.ts +7 -7
- package/src/mpp.ts +3 -3
- package/src/tools/executor.ts +125 -0
- package/src/tools/explore.ts +2 -3
- package/src/tools/tweetclaw.ts +3 -4
- package/src/types.ts +1 -1
- package/src/tools/sandbox.ts +0 -58
package/README.md
CHANGED
|
@@ -74,10 +74,10 @@ Top up credits from the Xquik dashboard or via `POST /credits/topup`. All 120 en
|
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
npm i mppx viem
|
|
77
|
-
openclaw config set plugins.entries.tweetclaw.config.
|
|
77
|
+
openclaw config set plugins.entries.tweetclaw.config.tempoSigningKey '0xYOUR_TEMPO_ACCOUNT_KEY'
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
MPP (Machine Payments Protocol) lets agents pay per API call via Tempo (USDC). No account, no API key, no subscription. 16 read-only endpoints.
|
|
80
|
+
MPP (Machine Payments Protocol) lets agents pay per API call via Tempo (USDC). No account, no API key, no subscription. 16 read-only endpoints. Create a Tempo account with `mppx account create` or at [tempo.xyz](https://tempo.xyz). The key stays local and is only used to sign payment proofs.
|
|
81
81
|
|
|
82
82
|
MPP-eligible endpoints: tweet lookup ($0.00015), tweet search ($0.00015/tweet), user lookup ($0.00015), user tweets ($0.00015/tweet), follower check ($0.00105), article lookup ($0.00105), media download ($0.00015/media), trends ($0.00045), X trends ($0.00045), quotes ($0.00015/tweet), replies ($0.00015/tweet), retweeters ($0.00015/user), favoriters ($0.00015/user), thread ($0.00015/tweet), user likes ($0.00015/tweet), user media ($0.00015/tweet).
|
|
83
83
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "tweetclaw",
|
|
3
3
|
"name": "TweetClaw",
|
|
4
|
-
"version": "1.4.
|
|
5
|
-
"description": "Post tweets, reply, like, retweet, follow, DM from your chat - full X/Twitter automation powered by Xquik. 120 endpoints, reads from $0.00015/call.",
|
|
4
|
+
"version": "1.4.1",
|
|
5
|
+
"description": "Post tweets, reply, like, retweet, follow, DM from your chat - full X/Twitter automation powered by Xquik. 120 endpoints, reads from $0.00015/call. Requires an Xquik API key or Tempo signing key.",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"properties": {
|
|
10
10
|
"apiKey": { "type": "string", "minLength": 1, "description": "Xquik API key (get one at dashboard.xquik.com). Required for full access to all 120 endpoints." },
|
|
11
|
-
"
|
|
11
|
+
"tempoSigningKey": { "type": "string", "minLength": 1, "description": "Tempo signing key for MPP pay-per-use mode. No account needed. 16 read-only X-API endpoints." },
|
|
12
12
|
"baseUrl": { "type": "string", "default": "https://xquik.com" },
|
|
13
13
|
"pollingInterval": { "type": "number", "default": 60, "description": "Event polling interval in seconds" },
|
|
14
14
|
"pollingEnabled": { "type": "boolean", "default": true }
|
|
15
15
|
},
|
|
16
16
|
"anyOf": [
|
|
17
17
|
{ "required": ["apiKey"] },
|
|
18
|
-
{ "required": ["
|
|
18
|
+
{ "required": ["tempoSigningKey"] }
|
|
19
19
|
]
|
|
20
20
|
},
|
|
21
21
|
"uiHints": {
|
|
22
22
|
"apiKey": { "label": "Xquik API Key", "sensitive": true, "placeholder": "xq_...", "help": "Full access to all 120 endpoints. Generate at dashboard.xquik.com." },
|
|
23
|
-
"
|
|
23
|
+
"tempoSigningKey": { "label": "Tempo Signing Key (MPP)", "sensitive": true, "placeholder": "0x...", "help": "Pay-per-use mode via Machine Payments Protocol. No account needed. 16 read-only X-API endpoints only. Requires mppx and viem packages." },
|
|
24
24
|
"baseUrl": { "label": "API Base URL", "placeholder": "https://xquik.com", "advanced": true, "help": "Only change if using a self-hosted Xquik instance." },
|
|
25
25
|
"pollingInterval": { "label": "Event Poll Interval (seconds)", "advanced": true, "help": "How often to check for new monitor events. Default: 60 seconds." },
|
|
26
26
|
"pollingEnabled": { "label": "Enable Event Notifications", "advanced": true, "help": "Deliver monitor alerts and extraction completions to your chat." }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xquik/tweetclaw",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Post tweets, reply, like, retweet, follow, DM & more from OpenClaw - full X/Twitter automation via Xquik. 120 endpoints, reads from $0.00015/call.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,15 @@
|
|
|
18
18
|
"openclaw": {
|
|
19
19
|
"extensions": [
|
|
20
20
|
"./src/index.ts"
|
|
21
|
-
]
|
|
21
|
+
],
|
|
22
|
+
"compat": {
|
|
23
|
+
"pluginApi": ">=2026.3.28",
|
|
24
|
+
"minGatewayVersion": "2026.3.28"
|
|
25
|
+
},
|
|
26
|
+
"build": {
|
|
27
|
+
"openclawVersion": "2026.3.28",
|
|
28
|
+
"pluginSdkVersion": "2026.3.28"
|
|
29
|
+
}
|
|
22
30
|
},
|
|
23
31
|
"files": [
|
|
24
32
|
"src/",
|
|
@@ -102,10 +102,10 @@ Get a key at [dashboard.xquik.com](https://dashboard.xquik.com/).
|
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
104
|
npm i mppx viem
|
|
105
|
-
openclaw config set plugins.entries.tweetclaw.config.
|
|
105
|
+
openclaw config set plugins.entries.tweetclaw.config.tempoSigningKey '0xYOUR_SIGNING_KEY'
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
MPP gives agents access to 16 read-only X-API endpoints without any account or subscription. The mppx SDK handles HTTP 402 payment challenges automatically.
|
|
108
|
+
MPP gives agents access to 16 read-only X-API endpoints without any account or subscription. The mppx SDK handles HTTP 402 payment challenges automatically. The signing key stays local and is only used to sign payment proofs.
|
|
109
109
|
|
|
110
110
|
## Tools
|
|
111
111
|
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ function isPollerEvent(value: unknown): value is PollerEvent {
|
|
|
18
18
|
|
|
19
19
|
function isPluginConfig(value: unknown): value is PluginConfig {
|
|
20
20
|
if (typeof value !== 'object' || value === null) return false;
|
|
21
|
-
return 'apiKey' in value || '
|
|
21
|
+
return 'apiKey' in value || 'tempoSigningKey' in value;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const DEFAULT_POLLING_INTERVAL_SECONDS = 60;
|
|
@@ -76,25 +76,25 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
|
|
|
76
76
|
const config: unknown = api.pluginConfig;
|
|
77
77
|
if (!isPluginConfig(config)) {
|
|
78
78
|
api.logger.warn(
|
|
79
|
-
"TweetClaw: No API key or Tempo
|
|
79
|
+
"TweetClaw: No API key or Tempo signing key configured. Run: openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY' or set tempoSigningKey for MPP pay-per-use",
|
|
80
80
|
);
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const { apiKey, baseUrl = 'https://xquik.com',
|
|
85
|
-
const isMppMode = apiKey === undefined &&
|
|
84
|
+
const { apiKey, baseUrl = 'https://xquik.com', tempoSigningKey } = config;
|
|
85
|
+
const isMppMode = apiKey === undefined && tempoSigningKey !== undefined;
|
|
86
86
|
const credential = apiKey ?? '';
|
|
87
87
|
|
|
88
88
|
if (isMppMode) {
|
|
89
89
|
void (async (): Promise<void> => {
|
|
90
90
|
try {
|
|
91
|
-
await initMpp(
|
|
92
|
-
api.logger.info('TweetClaw: MPP initialized - Tempo
|
|
91
|
+
await initMpp(tempoSigningKey);
|
|
92
|
+
api.logger.info('TweetClaw: MPP initialized - Tempo account ready');
|
|
93
93
|
} catch (error: unknown) {
|
|
94
94
|
api.logger.error(`TweetClaw: MPP init failed - ${error instanceof Error ? error.message : String(error)}`);
|
|
95
95
|
}
|
|
96
96
|
})();
|
|
97
|
-
api.logger.info('TweetClaw: MPP mode - pay-per-use via Tempo (16 X-API endpoints, no subscription needed)');
|
|
97
|
+
api.logger.info('TweetClaw: MPP mode - pay-per-use via Tempo account (16 X-API endpoints, no subscription needed)');
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
const request = createProxiedRequest(baseUrl, credential, fetchFunction);
|
package/src/mpp.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveAsyncFunctionConstructor } from './tools/
|
|
1
|
+
import { resolveAsyncFunctionConstructor } from './tools/executor.js';
|
|
2
2
|
|
|
3
3
|
type ModuleLoader = (name: string) => Promise<Record<string, unknown>>;
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ function createModuleLoader(): ModuleLoader {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
async function initMpp(
|
|
25
|
+
async function initMpp(tempoSigningKey: string, loadModule?: ModuleLoader): Promise<void> {
|
|
26
26
|
const load = loadModule ?? createModuleLoader();
|
|
27
27
|
const mppxMod = await load('mppx/client').catch((): never => {
|
|
28
28
|
throw new Error('MPP requires mppx package. Run: npm i mppx viem');
|
|
@@ -35,7 +35,7 @@ async function initMpp(tempoPrivateKey: string, loadModule?: ModuleLoader): Prom
|
|
|
35
35
|
if (!isRecord(mppxMod.Mppx)) throw new Error('mppx missing Mppx');
|
|
36
36
|
const createMethod: unknown = mppxMod.Mppx.create;
|
|
37
37
|
if (!isCallable(createMethod)) throw new Error('mppx Mppx.create is not a function');
|
|
38
|
-
const account: unknown = viemMod.privateKeyToAccount(
|
|
38
|
+
const account: unknown = viemMod.privateKeyToAccount(tempoSigningKey);
|
|
39
39
|
const method: unknown = mppxMod.tempo({ account });
|
|
40
40
|
createMethod({ methods: [method] });
|
|
41
41
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import vm from 'node:vm';
|
|
2
|
+
import { API_SPEC } from '../api-spec.js';
|
|
3
|
+
import { truncateResponse } from '../truncate.js';
|
|
4
|
+
import type { ToolResult } from '../types.js';
|
|
5
|
+
|
|
6
|
+
const specEndpoints: ReadonlyArray<Readonly<Record<string, unknown>>> = API_SPEC.map(
|
|
7
|
+
(endpoint): Readonly<Record<string, unknown>> => ({ ...endpoint }),
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
function extractErrorMessage(error: unknown): string {
|
|
11
|
+
if (error instanceof Error) {
|
|
12
|
+
return `${error.constructor.name}: ${error.message}`;
|
|
13
|
+
}
|
|
14
|
+
return String(error);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isAsyncFunctionConstructor(
|
|
18
|
+
value: unknown,
|
|
19
|
+
): value is new (...parameters: readonly string[]) => (...parameters: readonly unknown[]) => Promise<unknown> {
|
|
20
|
+
return typeof value === 'function';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getConstructorFromPrototype(proto: unknown): unknown {
|
|
24
|
+
if (typeof proto !== 'object' || proto === null) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return 'constructor' in proto ? proto.constructor : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveAsyncFunctionConstructor(prototype?: unknown): new (
|
|
31
|
+
...parameters: readonly string[]
|
|
32
|
+
) => (...parameters: readonly unknown[]) => Promise<unknown> {
|
|
33
|
+
const asyncPrototype: unknown = prototype ?? Object.getPrototypeOf(async (): Promise<void> => {});
|
|
34
|
+
const candidate: unknown = getConstructorFromPrototype(asyncPrototype);
|
|
35
|
+
if (!isAsyncFunctionConstructor(candidate)) {
|
|
36
|
+
throw new Error('AsyncFunction constructor not found');
|
|
37
|
+
}
|
|
38
|
+
return candidate;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const BLOCKED_PROPS: ReadonlySet<string | symbol> = new Set(['constructor', '__proto__', 'prototype']);
|
|
42
|
+
|
|
43
|
+
function isBlockedProperty(property: string | symbol): boolean {
|
|
44
|
+
return BLOCKED_PROPS.has(property);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function wrapValue(value: unknown): unknown {
|
|
48
|
+
if (value === null || value === undefined) return value;
|
|
49
|
+
if (typeof value !== 'object' && typeof value !== 'function') return value;
|
|
50
|
+
return createSafeProxy(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function wrapAsync(promise: Promise<unknown>): Promise<unknown> {
|
|
54
|
+
const resolved: unknown = await promise;
|
|
55
|
+
return wrapValue(resolved);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createCallableProxy(bound: (...a: readonly unknown[]) => unknown): unknown {
|
|
59
|
+
const handler: ProxyHandler<typeof bound> = {
|
|
60
|
+
apply(_target: typeof bound, _thisArgument: unknown, argumentsList: unknown[]): unknown {
|
|
61
|
+
const result: unknown = bound(...argumentsList);
|
|
62
|
+
if (result instanceof Promise) {
|
|
63
|
+
return wrapAsync(result);
|
|
64
|
+
}
|
|
65
|
+
return wrapValue(result);
|
|
66
|
+
},
|
|
67
|
+
get(_target: typeof bound, property: string | symbol): unknown {
|
|
68
|
+
if (isBlockedProperty(property)) return undefined;
|
|
69
|
+
return Reflect.get(_target, property);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
return new Proxy(bound, handler);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function createSafeProxy(target: unknown): unknown {
|
|
76
|
+
if (target === null || target === undefined) return target;
|
|
77
|
+
if (typeof target !== 'object' && typeof target !== 'function') return target;
|
|
78
|
+
|
|
79
|
+
const handler: ProxyHandler<Record<string | symbol, unknown>> = {
|
|
80
|
+
get(t: Record<string | symbol, unknown>, property: string | symbol): unknown {
|
|
81
|
+
if (isBlockedProperty(property)) return undefined;
|
|
82
|
+
const value: unknown = Reflect.get(t, property);
|
|
83
|
+
if (typeof value === 'function') {
|
|
84
|
+
const bound: (...a: readonly unknown[]) => unknown = value.bind(t);
|
|
85
|
+
return createCallableProxy(bound);
|
|
86
|
+
}
|
|
87
|
+
return wrapValue(value);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
return new Proxy(target as Record<string | symbol, unknown>, handler);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function runInSandbox(code: string, globals: Readonly<Record<string, unknown>>): unknown {
|
|
94
|
+
const rawContext: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
95
|
+
for (const key of Object.keys(globals)) {
|
|
96
|
+
const value: unknown = globals[key];
|
|
97
|
+
const safeValue: unknown = typeof value === 'object' && value !== null
|
|
98
|
+
? createSafeProxy(value)
|
|
99
|
+
: value;
|
|
100
|
+
Reflect.set(rawContext, key, safeValue);
|
|
101
|
+
}
|
|
102
|
+
const context: vm.Context = vm.createContext(rawContext);
|
|
103
|
+
return vm.runInNewContext(`(${code})()`, context);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function successResult(content: unknown): ToolResult {
|
|
107
|
+
return { content: [{ text: truncateResponse(content), type: 'text' as const }] };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function errorResult(error: unknown): ToolResult {
|
|
111
|
+
return { content: [{ text: extractErrorMessage(error), type: 'text' as const }], isError: true };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
BLOCKED_PROPS,
|
|
116
|
+
createSafeProxy,
|
|
117
|
+
errorResult,
|
|
118
|
+
extractErrorMessage,
|
|
119
|
+
getConstructorFromPrototype,
|
|
120
|
+
resolveAsyncFunctionConstructor,
|
|
121
|
+
runInSandbox,
|
|
122
|
+
specEndpoints,
|
|
123
|
+
successResult,
|
|
124
|
+
wrapValue,
|
|
125
|
+
};
|
package/src/tools/explore.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { API_SPEC } from '../api-spec.js';
|
|
2
|
-
import {
|
|
2
|
+
import { errorResult, runInSandbox, specEndpoints, successResult } from './executor.js';
|
|
3
3
|
import type { EndpointInfo, ToolResult } from '../types.js';
|
|
4
4
|
|
|
5
5
|
const categories = [...new Set(API_SPEC.map((endpoint) => endpoint.category))].toSorted((a, b) => a.localeCompare(b)).join(', ');
|
|
@@ -47,8 +47,7 @@ async () => {
|
|
|
47
47
|
|
|
48
48
|
async function handleExplore(code: string): Promise<ToolResult> {
|
|
49
49
|
try {
|
|
50
|
-
const
|
|
51
|
-
const result: unknown = await executor({ endpoints: specEndpoints });
|
|
50
|
+
const result: unknown = await runInSandbox(code, { spec: { endpoints: specEndpoints } });
|
|
52
51
|
return successResult(result);
|
|
53
52
|
} catch (error: unknown) {
|
|
54
53
|
return errorResult(error);
|
package/src/tools/tweetclaw.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createProxiedRequest } from '../request.js';
|
|
2
|
-
import {
|
|
2
|
+
import { errorResult, runInSandbox, specEndpoints, successResult } from './executor.js';
|
|
3
3
|
import type { FetchFunction, RequestFunction, ToolResult } from '../types.js';
|
|
4
4
|
|
|
5
5
|
const EXECUTE_DESCRIPTION = `Execute X (Twitter) API calls: post tweets, reply, like, retweet, follow, DM, update profile, upload media, search tweets, look up users, extract data, run giveaways, monitor accounts, compose tweets, and more. Write an async arrow function.
|
|
@@ -302,7 +302,7 @@ async () => {
|
|
|
302
302
|
- Subscription required: /api/v1/styles (X API refresh when cache >7 days), /api/v1/x/profile, /api/v1/x/communities, /api/v1/x/dm, /api/v1/extractions, /api/v1/draws, /api/v1/monitors, /api/v1/events, /api/v1/webhooks, /api/v1/styles/:username/performance, /api/v1/trending/:source
|
|
303
303
|
- Write actions (subscription required): POST /api/v1/x/tweets, DELETE /api/v1/x/tweets/:id, POST|DELETE /api/v1/x/tweets/:id/like, POST /api/v1/x/tweets/:id/retweet, POST|DELETE /api/v1/x/users/:id/follow, POST /api/v1/x/dm/:userId, POST /api/v1/x/media, PATCH /api/v1/x/profile, PATCH /api/v1/x/profile/avatar, PATCH /api/v1/x/profile/banner, POST|DELETE /api/v1/x/communities, POST|DELETE /api/v1/x/communities/:id/join
|
|
304
304
|
- IMPORTANT: Always attempt the request. Never assume subscription status. The API returns a clear error if subscription is missing.
|
|
305
|
-
- MPP MODE: When configured with
|
|
305
|
+
- MPP MODE: When configured with a Tempo signing key (no API key), the mppx SDK auto-handles 402 challenges by paying via Tempo (USDC). Only the 16 MPP-eligible endpoints work in this mode.
|
|
306
306
|
|
|
307
307
|
## Error handling
|
|
308
308
|
- If response contains "subscription is inactive" or status 402, call POST /api/v1/subscribe to get checkout URL
|
|
@@ -325,10 +325,9 @@ async function handleTweetclaw(options: Readonly<TweetclawOptions>): Promise<Too
|
|
|
325
325
|
const { apiKey, baseUrl, code, fetchFunction, timeoutMs = EXECUTION_TIMEOUT_MS } = options;
|
|
326
326
|
try {
|
|
327
327
|
const request: RequestFunction = createProxiedRequest(baseUrl, apiKey, fetchFunction);
|
|
328
|
-
const executor = new AsyncFunction('xquik', 'spec', `return (${code})()`);
|
|
329
328
|
|
|
330
329
|
const result: unknown = await Promise.race([
|
|
331
|
-
|
|
330
|
+
runInSandbox(code, { xquik: { request }, spec: { endpoints: specEndpoints } }),
|
|
332
331
|
new Promise<never>((_resolve, reject) => {
|
|
333
332
|
setTimeout(() => {
|
|
334
333
|
reject(new Error(`Execution timed out after ${String(timeoutMs / MS_PER_SECOND)}s`));
|
package/src/types.ts
CHANGED
package/src/tools/sandbox.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { API_SPEC } from '../api-spec.js';
|
|
2
|
-
import { truncateResponse } from '../truncate.js';
|
|
3
|
-
import type { ToolResult } from '../types.js';
|
|
4
|
-
|
|
5
|
-
const specEndpoints: ReadonlyArray<Readonly<Record<string, unknown>>> = API_SPEC.map(
|
|
6
|
-
(endpoint): Readonly<Record<string, unknown>> => ({ ...endpoint }),
|
|
7
|
-
);
|
|
8
|
-
|
|
9
|
-
function extractErrorMessage(error: unknown): string {
|
|
10
|
-
if (error instanceof Error) {
|
|
11
|
-
return `${error.constructor.name}: ${error.message}`;
|
|
12
|
-
}
|
|
13
|
-
return String(error);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function isAsyncFunctionConstructor(
|
|
17
|
-
value: unknown,
|
|
18
|
-
): value is new (...parameters: readonly string[]) => (...parameters: readonly unknown[]) => Promise<unknown> {
|
|
19
|
-
return typeof value === 'function';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getConstructorFromPrototype(proto: unknown): unknown {
|
|
23
|
-
if (typeof proto !== 'object' || proto === null) {
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
return 'constructor' in proto ? proto.constructor : undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function resolveAsyncFunctionConstructor(prototype?: unknown): new (
|
|
30
|
-
...parameters: readonly string[]
|
|
31
|
-
) => (...parameters: readonly unknown[]) => Promise<unknown> {
|
|
32
|
-
const asyncPrototype: unknown = prototype ?? Object.getPrototypeOf(async (): Promise<void> => {});
|
|
33
|
-
const candidate: unknown = getConstructorFromPrototype(asyncPrototype);
|
|
34
|
-
if (!isAsyncFunctionConstructor(candidate)) {
|
|
35
|
-
throw new Error('AsyncFunction constructor not found');
|
|
36
|
-
}
|
|
37
|
-
return candidate;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const AsyncFunction = resolveAsyncFunctionConstructor();
|
|
41
|
-
|
|
42
|
-
function successResult(content: unknown): ToolResult {
|
|
43
|
-
return { content: [{ text: truncateResponse(content), type: 'text' as const }] };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function errorResult(error: unknown): ToolResult {
|
|
47
|
-
return { content: [{ text: extractErrorMessage(error), type: 'text' as const }], isError: true };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export {
|
|
51
|
-
AsyncFunction,
|
|
52
|
-
errorResult,
|
|
53
|
-
extractErrorMessage,
|
|
54
|
-
getConstructorFromPrototype,
|
|
55
|
-
resolveAsyncFunctionConstructor,
|
|
56
|
-
specEndpoints,
|
|
57
|
-
successResult,
|
|
58
|
-
};
|