taskforceai-sdk 1.2.0 → 1.3.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/LICENSE +37 -0
- package/README.md +24 -7
- package/dist/index.d.ts +22 -95
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +98 -187
- package/dist/transport.d.ts +20 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +106 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/package.json +25 -10
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -211
package/LICENSE
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
MIT License with Attribution Requirement for Large-Scale Commercial Use
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Clay Warren
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
ADDITIONAL COMMERCIAL ATTRIBUTION REQUIREMENT:
|
|
16
|
+
If you use this Software, in whole or in part, in any product, service, or
|
|
17
|
+
application that serves more than 100,000 users, you must:
|
|
18
|
+
|
|
19
|
+
1. Provide clear attribution to Clay Warren as the original creator
|
|
20
|
+
2. Include a statement that your product uses the "TaskForceAI" framework
|
|
21
|
+
3. This attribution must be visible in one of the following locations:
|
|
22
|
+
- Product documentation
|
|
23
|
+
- About page/section
|
|
24
|
+
- Credits section
|
|
25
|
+
- Help/support documentation
|
|
26
|
+
- Or equivalent user-accessible location
|
|
27
|
+
|
|
28
|
+
Example attribution: "Powered by TaskForceAI by Clay Warren"
|
|
29
|
+
or "Uses TaskForceAI multi-agent system created by Clay Warren"
|
|
30
|
+
|
|
31
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
37
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -22,22 +22,38 @@ const client = new TaskForceAI({
|
|
|
22
22
|
// Submit a task (using default free model)
|
|
23
23
|
const taskId = await client.submitTask('Test prompt');
|
|
24
24
|
|
|
25
|
-
// Or submit with your own
|
|
25
|
+
// Or submit with your own Vercel AI Gateway key for premium models
|
|
26
26
|
const taskId2 = await client.submitTask('Complex analysis', {
|
|
27
|
-
|
|
27
|
+
vercelAiKey: 'your-vercel-ai-key-here',
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
// Wait for completion
|
|
31
31
|
const result = await client.waitForCompletion(taskId);
|
|
32
|
-
|
|
32
|
+
// handle result.result here
|
|
33
33
|
|
|
34
34
|
// Or stream status updates
|
|
35
35
|
const stream = client.streamTaskStatus(taskId);
|
|
36
36
|
for await (const status of stream) {
|
|
37
|
-
|
|
37
|
+
// handle incremental status updates
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Mock Mode
|
|
42
|
+
|
|
43
|
+
Build and test your integration without an API key using mock mode:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { TaskForceAI } from 'taskforceai-sdk';
|
|
47
|
+
|
|
48
|
+
// No API key required in mock mode
|
|
49
|
+
const client = new TaskForceAI({ mockMode: true });
|
|
50
|
+
|
|
51
|
+
const result = await client.runTask('Test your integration');
|
|
52
|
+
console.log(result.result); // "This is a mock response. Configure your API key to get real results."
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Mock mode simulates the full task lifecycle locally—no network requests are made. Tasks go through "processing" then "completed" states, making it easy to build UIs and test error handling before launch.
|
|
56
|
+
|
|
41
57
|
## API Reference
|
|
42
58
|
|
|
43
59
|
### TaskForceAI
|
|
@@ -52,10 +68,11 @@ constructor(options: TaskForceAIOptions)
|
|
|
52
68
|
|
|
53
69
|
**Options:**
|
|
54
70
|
|
|
55
|
-
- `apiKey` (required): Your API key
|
|
71
|
+
- `apiKey` (required unless `mockMode` is true): Your API key
|
|
56
72
|
- `baseUrl` (optional): API base URL (default: https://taskforceai.chat/api/developer)
|
|
57
73
|
- `timeout` (optional): Request timeout in milliseconds (default: 30000)
|
|
58
74
|
- `responseHook` (optional): Callback invoked with every raw `fetch` response
|
|
75
|
+
- `mockMode` (optional): Enable mock mode for development without an API key (default: false)
|
|
59
76
|
|
|
60
77
|
#### Methods
|
|
61
78
|
|
|
@@ -75,7 +92,7 @@ async submitTask(
|
|
|
75
92
|
- `prompt`: The user's input prompt
|
|
76
93
|
- `options.silent`: Suppress logging (default: false)
|
|
77
94
|
- `options.mock`: Use mock responses for testing (default: false)
|
|
78
|
-
- `options.
|
|
95
|
+
- `options.vercelAiKey`: Supply your own Vercel AI Gateway API key to pick premium models
|
|
79
96
|
- `options.*`: Any additional TaskForceAI orchestration flags are forwarded untouched
|
|
80
97
|
|
|
81
98
|
**Returns:** Task ID string
|
|
@@ -155,7 +172,7 @@ try {
|
|
|
155
172
|
const result = await client.runTask('Your prompt');
|
|
156
173
|
} catch (error) {
|
|
157
174
|
if (error instanceof TaskForceAIError) {
|
|
158
|
-
|
|
175
|
+
// handle API error, inspect error.statusCode and error.details
|
|
159
176
|
}
|
|
160
177
|
}
|
|
161
178
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,98 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export interface TaskForceAIOptions {
|
|
6
|
-
apiKey: string;
|
|
7
|
-
baseUrl?: string;
|
|
8
|
-
timeout?: number;
|
|
9
|
-
responseHook?: TaskResponseHook;
|
|
10
|
-
}
|
|
11
|
-
export type TaskSubmissionOptions = {
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
silent?: boolean;
|
|
14
|
-
mock?: boolean;
|
|
15
|
-
openRouterKey?: string;
|
|
16
|
-
};
|
|
17
|
-
export interface TaskStatus {
|
|
18
|
-
taskId: string;
|
|
19
|
-
status: 'processing' | 'completed' | 'failed';
|
|
20
|
-
result?: string;
|
|
21
|
-
error?: string;
|
|
22
|
-
warnings?: string[];
|
|
23
|
-
metadata?: Record<string, unknown>;
|
|
24
|
-
}
|
|
25
|
-
export interface TaskResult extends TaskStatus {
|
|
26
|
-
status: 'completed';
|
|
27
|
-
result: string;
|
|
28
|
-
}
|
|
29
|
-
export type TaskStatusCallback = (status: TaskStatus) => void;
|
|
30
|
-
export type TaskResponseHook = (response: Response) => void;
|
|
31
|
-
export declare class TaskStatusStream implements AsyncIterable<TaskStatus> {
|
|
32
|
-
private readonly client;
|
|
33
|
-
readonly taskId: string;
|
|
34
|
-
private readonly pollInterval;
|
|
35
|
-
private readonly maxAttempts;
|
|
36
|
-
private readonly onStatus?;
|
|
37
|
-
private cancelled;
|
|
38
|
-
constructor(client: TaskForceAI, taskId: string, pollInterval: number, maxAttempts: number, onStatus?: TaskStatusCallback | undefined);
|
|
39
|
-
cancel(): void;
|
|
40
|
-
[Symbol.asyncIterator](): AsyncIterator<TaskStatus>;
|
|
41
|
-
}
|
|
42
|
-
export declare class TaskForceAIError extends Error {
|
|
43
|
-
statusCode?: number | undefined;
|
|
44
|
-
constructor(message: string, statusCode?: number | undefined);
|
|
45
|
-
}
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { TaskForceAIError, transportDefaults as def } from './transport';
|
|
3
|
+
import type { TaskForceAIOptions, TaskResult, TaskStatus, TaskStatusCallback, TaskStatusStream, TaskSubmissionOptions } from './types';
|
|
4
|
+
import { VERSION } from './types';
|
|
46
5
|
export declare class TaskForceAI {
|
|
47
|
-
private
|
|
48
|
-
private
|
|
49
|
-
private
|
|
50
|
-
private
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* @param taskId - The task ID returned from submitTask
|
|
65
|
-
* @returns Promise<TaskStatus> - The current task status
|
|
66
|
-
*/
|
|
67
|
-
getTaskStatus(taskId: string): Promise<TaskStatus>;
|
|
68
|
-
/**
|
|
69
|
-
* Get the final result of a completed task.
|
|
70
|
-
*
|
|
71
|
-
* @param taskId - The task ID returned from submitTask
|
|
72
|
-
* @returns Promise<TaskResult> - The task result
|
|
73
|
-
*/
|
|
74
|
-
getTaskResult(taskId: string): Promise<TaskResult>;
|
|
75
|
-
/**
|
|
76
|
-
* Wait for a task to complete and return the result.
|
|
77
|
-
* This method polls the status until the task is completed or failed.
|
|
78
|
-
*
|
|
79
|
-
* @param taskId - The task ID returned from submitTask
|
|
80
|
-
* @param pollInterval - Interval between status checks in milliseconds (default: 2000)
|
|
81
|
-
* @param maxAttempts - Maximum number of polling attempts (default: 150)
|
|
82
|
-
* @returns Promise<TaskResult> - The final task result
|
|
83
|
-
*/
|
|
84
|
-
waitForCompletion(taskId: string, pollInterval?: number, maxAttempts?: number, onStatus?: TaskStatusCallback): Promise<TaskResult>;
|
|
85
|
-
/**
|
|
86
|
-
* Submit a task and wait for completion in one call.
|
|
87
|
-
*
|
|
88
|
-
* @param prompt - The user's input prompt
|
|
89
|
-
* @param options - Optional settings for the task
|
|
90
|
-
* @param pollInterval - Interval between status checks in milliseconds (default: 2000)
|
|
91
|
-
* @param maxAttempts - Maximum number of polling attempts (default: 150)
|
|
92
|
-
* @returns Promise<TaskResult> - The final task result
|
|
93
|
-
*/
|
|
94
|
-
runTask(prompt: string, options?: TaskSubmissionOptions, pollInterval?: number, maxAttempts?: number, onStatus?: TaskStatusCallback): Promise<TaskResult>;
|
|
95
|
-
streamTaskStatus(taskId: string, pollInterval?: number, maxAttempts?: number, onStatus?: TaskStatusCallback): TaskStatusStream;
|
|
96
|
-
runTaskStream(prompt: string, options?: TaskSubmissionOptions, pollInterval?: number, maxAttempts?: number, onStatus?: TaskStatusCallback): Promise<TaskStatusStream>;
|
|
6
|
+
private ak;
|
|
7
|
+
private url;
|
|
8
|
+
private t;
|
|
9
|
+
private rh?;
|
|
10
|
+
private mm;
|
|
11
|
+
private mcc;
|
|
12
|
+
constructor(o: TaskForceAIOptions);
|
|
13
|
+
private mockResponse;
|
|
14
|
+
private req;
|
|
15
|
+
submitTask(p: string, o?: TaskSubmissionOptions): Promise<string>;
|
|
16
|
+
getTaskStatus(id: any): Promise<TaskStatus>;
|
|
17
|
+
getTaskResult(id: any): Promise<TaskResult>;
|
|
18
|
+
private poll;
|
|
19
|
+
waitForCompletion(id: string, ms?: 2000, max?: 150, on?: TaskStatusCallback, sig?: AbortSignal): Promise<TaskResult>;
|
|
20
|
+
runTask(p: string, o?: TaskSubmissionOptions, ms?: 2000, max?: 150, on?: TaskStatusCallback): Promise<TaskResult>;
|
|
21
|
+
streamTaskStatus(id: any, ms?: 2000, max?: 150, on?: TaskStatusCallback, sig?: AbortSignal): TaskStatusStream;
|
|
22
|
+
runTaskStream(p: string, o?: TaskSubmissionOptions, ms?: 2000, max?: 150, on?: TaskStatusCallback, sig?: AbortSignal): Promise<TaskStatusStream>;
|
|
97
23
|
}
|
|
24
|
+
export { TaskForceAIError, TaskStatus, TaskResult, TaskSubmissionOptions, TaskStatusCallback, TaskStatusStream, TaskForceAIOptions, VERSION, def as transportDefaults, };
|
|
98
25
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,IAAI,GAAG,EAAe,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EACV,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AASlC,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,CAAC,CAAS;IAClB,OAAO,CAAC,EAAE,CAAC,CAAwB;IACnC,OAAO,CAAC,EAAE,CAAU;IACpB,OAAO,CAAC,GAAG,CAAkC;gBAEjC,CAAC,EAAE,kBAAkB;IAWjC,OAAO,CAAC,YAAY;IAsBpB,OAAO,CAAC,GAAG,CAWT;IAEI,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAE,qBAA0B,GAAG,OAAO,CAAC,MAAM,CAAC;IAWrE,aAAa,CAAC,EAAE,EAAE,GAAG;IAKrB,aAAa,CAAC,EAAE,EAAE,GAAG;YAMZ,IAAI;IAoBb,iBAAiB,CACrB,EAAE,EAAE,MAAM,EACV,EAAE,OAAqB,EACvB,GAAG,MAAsB,EACzB,EAAE,CAAC,EAAE,kBAAkB,EACvB,GAAG,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,UAAU,CAAC;IAQhB,OAAO,CACX,CAAC,EAAE,MAAM,EACT,CAAC,GAAE,qBAA0B,EAC7B,EAAE,OAAqB,EACvB,GAAG,MAAsB,EACzB,EAAE,CAAC,EAAE,kBAAkB;IAKzB,gBAAgB,CACd,EAAE,EAAE,GAAG,EACP,EAAE,OAAqB,EACvB,GAAG,MAAsB,EACzB,EAAE,CAAC,EAAE,kBAAkB,EACvB,GAAG,CAAC,EAAE,WAAW,GAChB,gBAAgB;IAgBb,aAAa,CACjB,CAAC,EAAE,MAAM,EACT,CAAC,GAAE,qBAA0B,EAC7B,EAAE,OAAqB,EACvB,GAAG,MAAsB,EACzB,EAAE,CAAC,EAAE,kBAAkB,EACvB,GAAG,CAAC,EAAE,WAAW;CAIpB;AAED,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EACP,GAAG,IAAI,iBAAiB,GACzB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,207 +1,118 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
this.maxAttempts = maxAttempts;
|
|
14
|
-
this.onStatus = onStatus;
|
|
15
|
-
this.cancelled = false;
|
|
16
|
-
}
|
|
17
|
-
cancel() {
|
|
18
|
-
this.cancelled = true;
|
|
19
|
-
}
|
|
20
|
-
async *[Symbol.asyncIterator]() {
|
|
21
|
-
for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
|
|
22
|
-
if (this.cancelled) {
|
|
23
|
-
throw new TaskForceAIError('Task stream cancelled');
|
|
24
|
-
}
|
|
25
|
-
const status = await this.client.getTaskStatus(this.taskId);
|
|
26
|
-
this.onStatus?.(status);
|
|
27
|
-
yield status;
|
|
28
|
-
if (status.status === 'completed' || status.status === 'failed') {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
await new Promise((resolve) => setTimeout(resolve, this.pollInterval));
|
|
32
|
-
}
|
|
33
|
-
throw new TaskForceAIError('Task did not complete within the expected time');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
exports.TaskStatusStream = TaskStatusStream;
|
|
37
|
-
class TaskForceAIError extends Error {
|
|
38
|
-
constructor(message, statusCode) {
|
|
39
|
-
super(message);
|
|
40
|
-
this.statusCode = statusCode;
|
|
41
|
-
this.name = 'TaskForceAIError';
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
exports.TaskForceAIError = TaskForceAIError;
|
|
3
|
+
exports.transportDefaults = exports.VERSION = exports.TaskForceAIError = exports.TaskForceAI = void 0;
|
|
4
|
+
const transport_1 = require("./transport");
|
|
5
|
+
Object.defineProperty(exports, "TaskForceAIError", { enumerable: true, get: function () { return transport_1.TaskForceAIError; } });
|
|
6
|
+
Object.defineProperty(exports, "transportDefaults", { enumerable: true, get: function () { return transport_1.transportDefaults; } });
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return types_1.VERSION; } });
|
|
9
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
10
|
+
setTimeout(() => resolve(), ms);
|
|
11
|
+
});
|
|
12
|
+
const MOCK_RESULT = 'This is a mock response. Configure your API key to get real results.';
|
|
45
13
|
class TaskForceAI {
|
|
46
|
-
constructor(
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
async makeRequest(endpoint, options = {}) {
|
|
53
|
-
// @ts-ignore - TypeScript has issues with generic JSON parsing
|
|
54
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
55
|
-
const controller = new AbortController();
|
|
56
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
57
|
-
try {
|
|
58
|
-
const response = await fetch(url, {
|
|
59
|
-
...options,
|
|
60
|
-
headers: {
|
|
61
|
-
'Content-Type': 'application/json',
|
|
62
|
-
'x-api-key': this.apiKey,
|
|
63
|
-
...options.headers,
|
|
64
|
-
},
|
|
65
|
-
signal: controller.signal,
|
|
66
|
-
});
|
|
67
|
-
clearTimeout(timeoutId);
|
|
68
|
-
if (this.responseHook) {
|
|
69
|
-
const cloned = response.clone();
|
|
70
|
-
this.responseHook(cloned);
|
|
14
|
+
constructor(o) {
|
|
15
|
+
this.mcc = new Map();
|
|
16
|
+
this.req = (e, i = {}, r = false) => {
|
|
17
|
+
if (this.mm) {
|
|
18
|
+
return Promise.resolve(this.mockResponse(e, i.method || 'GET'));
|
|
71
19
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const errorData = JSON.parse(errorText);
|
|
78
|
-
if (errorData && typeof errorData === 'object' && 'error' in errorData) {
|
|
79
|
-
errorMessage = errorData.error;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
// Ignore JSON parse errors
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Ignore JSON parse errors, use default message
|
|
88
|
-
}
|
|
89
|
-
throw new TaskForceAIError(errorMessage, response.status);
|
|
90
|
-
}
|
|
91
|
-
const data = await response.json();
|
|
92
|
-
return data;
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
clearTimeout(timeoutId);
|
|
96
|
-
if (error instanceof TaskForceAIError) {
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
99
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
100
|
-
throw new TaskForceAIError('Request timeout');
|
|
101
|
-
}
|
|
102
|
-
throw new TaskForceAIError(`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
20
|
+
return (0, transport_1.makeRequest)(e, i, { apiKey: this.ak, baseUrl: this.url, timeout: this.t, responseHook: this.rh }, r, transport_1.transportDefaults.maxRetries);
|
|
21
|
+
};
|
|
22
|
+
this.mm = o.mockMode ?? false;
|
|
23
|
+
if (!this.mm && !o.apiKey) {
|
|
24
|
+
throw new transport_1.TaskForceAIError('API key is required when not in mock mode');
|
|
103
25
|
}
|
|
26
|
+
this.ak = o.apiKey || '';
|
|
27
|
+
this.url = o.baseUrl || 'https://taskforceai.chat/api/developer';
|
|
28
|
+
this.t = o.timeout || transport_1.transportDefaults.timeout;
|
|
29
|
+
this.rh = o.responseHook;
|
|
104
30
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
* @returns Promise<string> - The task ID
|
|
111
|
-
*/
|
|
112
|
-
async submitTask(prompt, options = {}) {
|
|
113
|
-
if (!prompt || typeof prompt !== 'string') {
|
|
114
|
-
throw new TaskForceAIError('Prompt must be a non-empty string');
|
|
31
|
+
mockResponse(e, method) {
|
|
32
|
+
if (method === 'POST' && e === '/run') {
|
|
33
|
+
const taskId = `mock-${Math.random().toString(36).slice(2, 10)}`;
|
|
34
|
+
this.mcc.set(taskId, 0);
|
|
35
|
+
return { taskId, status: 'processing' };
|
|
115
36
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
payload.options = normalizedOptions;
|
|
37
|
+
if (e.startsWith('/status/')) {
|
|
38
|
+
const taskId = e.split('/').pop();
|
|
39
|
+
const count = this.mcc.get(taskId) || 0;
|
|
40
|
+
this.mcc.set(taskId, count + 1);
|
|
41
|
+
if (count < 1) {
|
|
42
|
+
return { taskId, status: 'processing', message: 'Mock task processing...' };
|
|
43
|
+
}
|
|
44
|
+
return { taskId, status: 'completed', result: MOCK_RESULT };
|
|
125
45
|
}
|
|
126
|
-
if (
|
|
127
|
-
|
|
46
|
+
if (e.startsWith('/results/')) {
|
|
47
|
+
const taskId = e.split('/').pop();
|
|
48
|
+
return { taskId, status: 'completed', result: MOCK_RESULT };
|
|
128
49
|
}
|
|
129
|
-
|
|
130
|
-
method: 'POST',
|
|
131
|
-
body: JSON.stringify(payload),
|
|
132
|
-
});
|
|
133
|
-
return response.taskId;
|
|
50
|
+
return { status: 'ok' };
|
|
134
51
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
52
|
+
async submitTask(p, o = {}) {
|
|
53
|
+
if (typeof p !== 'string' || !p.trim())
|
|
54
|
+
throw new transport_1.TaskForceAIError('Prompt must be a non-empty string');
|
|
55
|
+
const { vercelAiKey: v, silent: s = false, mock: m = false, ...rest } = o;
|
|
56
|
+
const body = { prompt: p, options: { silent: s, mock: m, ...rest } };
|
|
57
|
+
if (v)
|
|
58
|
+
body.vercelAiKey = v;
|
|
59
|
+
return (await this.req('/run', { method: 'POST', body: JSON.stringify(body) })).taskId;
|
|
60
|
+
}
|
|
61
|
+
async getTaskStatus(id) {
|
|
62
|
+
if (!id || typeof id !== 'string')
|
|
63
|
+
throw new transport_1.TaskForceAIError('Task ID must be a non-empty string');
|
|
64
|
+
return this.req(`/status/${id}`, {}, true);
|
|
146
65
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
66
|
+
async getTaskResult(id) {
|
|
67
|
+
if (!id || typeof id !== 'string')
|
|
68
|
+
throw new transport_1.TaskForceAIError('Task ID must be a non-empty string');
|
|
69
|
+
return this.req(`/results/${id}`);
|
|
70
|
+
}
|
|
71
|
+
async *poll(id, ms, max, on, sig) {
|
|
72
|
+
for (let i = 0; i < max; i++) {
|
|
73
|
+
if (sig?.aborted)
|
|
74
|
+
throw new transport_1.TaskForceAIError('Task polling cancelled');
|
|
75
|
+
// eslint-disable-next-line no-await-in-loop
|
|
76
|
+
const s = await this.getTaskStatus(id);
|
|
77
|
+
on?.(s);
|
|
78
|
+
yield s;
|
|
79
|
+
if (['completed', 'failed'].includes(s.status))
|
|
80
|
+
return;
|
|
81
|
+
// eslint-disable-next-line no-await-in-loop
|
|
82
|
+
await sleep(ms);
|
|
156
83
|
}
|
|
157
|
-
|
|
84
|
+
throw new transport_1.TaskForceAIError('Task did not complete within the expected time');
|
|
158
85
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
* @param maxAttempts - Maximum number of polling attempts (default: 150)
|
|
166
|
-
* @returns Promise<TaskResult> - The final task result
|
|
167
|
-
*/
|
|
168
|
-
async waitForCompletion(taskId, pollInterval = 2000, maxAttempts = 150, onStatus) {
|
|
169
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
170
|
-
const status = await this.getTaskStatus(taskId);
|
|
171
|
-
onStatus?.(status);
|
|
172
|
-
if (status.status === 'completed' && status.result !== undefined) {
|
|
173
|
-
return status;
|
|
174
|
-
}
|
|
175
|
-
if (status.status === 'failed') {
|
|
176
|
-
throw new TaskForceAIError(status.error || 'Task failed');
|
|
177
|
-
}
|
|
178
|
-
// Wait before next poll
|
|
179
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
86
|
+
async waitForCompletion(id, ms = transport_1.transportDefaults.pollIntervalMs, max = transport_1.transportDefaults.maxPollAttempts, on, sig) {
|
|
87
|
+
for await (const s of this.poll(id, ms, max, on, sig)) {
|
|
88
|
+
if (s.status === 'completed' && s.result)
|
|
89
|
+
return s;
|
|
90
|
+
if (s.status === 'failed')
|
|
91
|
+
throw new transport_1.TaskForceAIError(s.error || 'Task failed');
|
|
180
92
|
}
|
|
181
|
-
throw new TaskForceAIError('Task did not complete within the expected time');
|
|
93
|
+
throw new transport_1.TaskForceAIError('Task did not complete within the expected time');
|
|
182
94
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
*
|
|
186
|
-
* @param prompt - The user's input prompt
|
|
187
|
-
* @param options - Optional settings for the task
|
|
188
|
-
* @param pollInterval - Interval between status checks in milliseconds (default: 2000)
|
|
189
|
-
* @param maxAttempts - Maximum number of polling attempts (default: 150)
|
|
190
|
-
* @returns Promise<TaskResult> - The final task result
|
|
191
|
-
*/
|
|
192
|
-
async runTask(prompt, options = {}, pollInterval = 2000, maxAttempts = 150, onStatus) {
|
|
193
|
-
const taskId = await this.submitTask(prompt, options);
|
|
194
|
-
return this.waitForCompletion(taskId, pollInterval, maxAttempts, onStatus);
|
|
95
|
+
async runTask(p, o = {}, ms = transport_1.transportDefaults.pollIntervalMs, max = transport_1.transportDefaults.maxPollAttempts, on) {
|
|
96
|
+
return this.waitForCompletion(await this.submitTask(p, o), ms, max, on);
|
|
195
97
|
}
|
|
196
|
-
streamTaskStatus(
|
|
197
|
-
if (!
|
|
198
|
-
throw new TaskForceAIError('Task ID must be a non-empty string');
|
|
199
|
-
|
|
200
|
-
return
|
|
98
|
+
streamTaskStatus(id, ms = transport_1.transportDefaults.pollIntervalMs, max = transport_1.transportDefaults.maxPollAttempts, on, sig) {
|
|
99
|
+
if (!id || typeof id !== 'string')
|
|
100
|
+
throw new transport_1.TaskForceAIError('Task ID must be a non-empty string');
|
|
101
|
+
let cancel = false;
|
|
102
|
+
return {
|
|
103
|
+
taskId: id,
|
|
104
|
+
cancel: () => (cancel = true),
|
|
105
|
+
[Symbol.asyncIterator]: async function* () {
|
|
106
|
+
for await (const s of this.poll(id, ms, max, on, sig)) {
|
|
107
|
+
if (cancel)
|
|
108
|
+
throw new transport_1.TaskForceAIError('Task stream cancelled');
|
|
109
|
+
yield s;
|
|
110
|
+
}
|
|
111
|
+
}.bind(this),
|
|
112
|
+
};
|
|
201
113
|
}
|
|
202
|
-
async runTaskStream(
|
|
203
|
-
|
|
204
|
-
return new TaskStatusStream(this, taskId, pollInterval, maxAttempts, onStatus);
|
|
114
|
+
async runTaskStream(p, o = {}, ms = transport_1.transportDefaults.pollIntervalMs, max = transport_1.transportDefaults.maxPollAttempts, on, sig) {
|
|
115
|
+
return this.streamTaskStatus(await this.submitTask(p, o), ms, max, on, sig);
|
|
205
116
|
}
|
|
206
117
|
}
|
|
207
118
|
exports.TaskForceAI = TaskForceAI;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export declare class TaskForceAIError extends Error {
|
|
3
|
+
statusCode?: number | undefined;
|
|
4
|
+
constructor(message: string, statusCode?: number | undefined);
|
|
5
|
+
}
|
|
6
|
+
export interface TransportConfig {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
timeout: number;
|
|
10
|
+
responseHook?: (response: Response) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const makeRequest: <T>(endpoint: string, options: RequestInit, { apiKey, baseUrl, timeout, responseHook }: TransportConfig, retryable?: boolean, maxRetries?: number) => Promise<T>;
|
|
13
|
+
export declare const transportDefaults: {
|
|
14
|
+
readonly timeout: 30000;
|
|
15
|
+
readonly maxRetries: 3;
|
|
16
|
+
readonly backoffMs: 500;
|
|
17
|
+
readonly pollIntervalMs: 2000;
|
|
18
|
+
readonly maxPollAttempts: 150;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":";AAsCA,qBAAa,gBAAiB,SAAQ,KAAK;IAGhC,UAAU,CAAC;gBADlB,OAAO,EAAE,MAAM,EACR,UAAU,CAAC,oBAAQ;CAK7B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC7C;AAED,eAAO,MAAM,WAAW,gBACZ,MAAM,WACP,WAAW,8CACwB,eAAe,yDA4D5D,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;CAMpB,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transportDefaults = exports.makeRequest = exports.TaskForceAIError = void 0;
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
5
|
+
const DEFAULT_BACKOFF_MS = 500;
|
|
6
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
7
|
+
const sleep = (ms) => new Promise((resolve) => {
|
|
8
|
+
setTimeout(() => resolve(), ms);
|
|
9
|
+
});
|
|
10
|
+
const buildSignal = (timeoutMs, externalSignal) => {
|
|
11
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
12
|
+
return externalSignal ? AbortSignal.any([timeoutSignal, externalSignal]) : timeoutSignal;
|
|
13
|
+
};
|
|
14
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
15
|
+
const parseErrorMessage = async (response) => {
|
|
16
|
+
try {
|
|
17
|
+
const errorText = await response.text();
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(errorText);
|
|
20
|
+
if (isRecord(parsed)) {
|
|
21
|
+
if (typeof parsed['error'] === 'string') {
|
|
22
|
+
return parsed['error'];
|
|
23
|
+
}
|
|
24
|
+
return `HTTP ${response.status}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Not JSON, fall back to text
|
|
29
|
+
}
|
|
30
|
+
if (errorText.trim().length > 0)
|
|
31
|
+
return errorText;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* ignore body errors */
|
|
35
|
+
}
|
|
36
|
+
return `HTTP ${response.status}`;
|
|
37
|
+
};
|
|
38
|
+
class TaskForceAIError extends Error {
|
|
39
|
+
constructor(message, statusCode) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.statusCode = statusCode;
|
|
42
|
+
this.name = 'TaskForceAIError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.TaskForceAIError = TaskForceAIError;
|
|
46
|
+
const makeRequest = async (endpoint, options, { apiKey, baseUrl, timeout, responseHook }, retryable = false, maxRetries = DEFAULT_MAX_RETRIES) => {
|
|
47
|
+
const url = `${baseUrl}${endpoint}`;
|
|
48
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
49
|
+
try {
|
|
50
|
+
// eslint-disable-next-line no-await-in-loop
|
|
51
|
+
const response = await fetch(url, {
|
|
52
|
+
...options,
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
'x-api-key': apiKey,
|
|
56
|
+
...options.headers,
|
|
57
|
+
},
|
|
58
|
+
signal: buildSignal(timeout, options.signal),
|
|
59
|
+
});
|
|
60
|
+
if (responseHook) {
|
|
61
|
+
try {
|
|
62
|
+
responseHook(response.clone());
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* ignore hook errors */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
// eslint-disable-next-line no-await-in-loop
|
|
70
|
+
const errorMessage = await parseErrorMessage(response);
|
|
71
|
+
const shouldRetry = retryable && response.status >= 500 && response.status < 600 && attempt < maxRetries;
|
|
72
|
+
if (shouldRetry) {
|
|
73
|
+
// eslint-disable-next-line no-await-in-loop
|
|
74
|
+
await sleep(DEFAULT_BACKOFF_MS * (attempt + 1));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
throw new TaskForceAIError(errorMessage, response.status);
|
|
78
|
+
}
|
|
79
|
+
// eslint-disable-next-line no-await-in-loop
|
|
80
|
+
const data = (await response.json());
|
|
81
|
+
return data;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof TaskForceAIError)
|
|
85
|
+
throw error;
|
|
86
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
87
|
+
throw new TaskForceAIError('Request timeout');
|
|
88
|
+
}
|
|
89
|
+
if (retryable && attempt < maxRetries) {
|
|
90
|
+
// eslint-disable-next-line no-await-in-loop
|
|
91
|
+
await sleep(DEFAULT_BACKOFF_MS * (attempt + 1));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
throw new TaskForceAIError(`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
throw new TaskForceAIError('Request failed after maximum retries');
|
|
98
|
+
};
|
|
99
|
+
exports.makeRequest = makeRequest;
|
|
100
|
+
exports.transportDefaults = {
|
|
101
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
102
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
103
|
+
backoffMs: DEFAULT_BACKOFF_MS,
|
|
104
|
+
pollIntervalMs: 2000,
|
|
105
|
+
maxPollAttempts: 150,
|
|
106
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export declare const VERSION = "1.2.1";
|
|
3
|
+
export interface TaskForceAIOptions {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
responseHook?: TaskResponseHook;
|
|
8
|
+
mockMode?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export type TaskSubmissionOptions = {
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
silent?: boolean;
|
|
13
|
+
mock?: boolean;
|
|
14
|
+
vercelAiKey?: string;
|
|
15
|
+
};
|
|
16
|
+
export interface TaskStatus {
|
|
17
|
+
taskId: string;
|
|
18
|
+
status: 'processing' | 'completed' | 'failed';
|
|
19
|
+
result?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
warnings?: string[];
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface TaskResult extends TaskStatus {
|
|
25
|
+
status: 'completed';
|
|
26
|
+
result: string;
|
|
27
|
+
}
|
|
28
|
+
export type TaskStatusCallback = (status: TaskStatus) => void;
|
|
29
|
+
export type TaskResponseHook = (response: Response) => void;
|
|
30
|
+
export interface TaskStatusStream extends AsyncIterable<TaskStatus> {
|
|
31
|
+
taskId: string;
|
|
32
|
+
cancel(): void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;AAE5D,MAAM,WAAW,gBAAiB,SAAQ,aAAa,CAAC,UAAU,CAAC;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,IAAI,IAAI,CAAC;CAChB"}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taskforceai-sdk",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "TypeScript SDK for TaskForceAI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
7
13
|
"scripts": {
|
|
8
14
|
"build": "tsc",
|
|
9
|
-
"test": "
|
|
10
|
-
"test:watch": "
|
|
15
|
+
"test": "bun test --preload ../../tests/bun-setup.ts",
|
|
16
|
+
"test:watch": "bun test --watch --preload ../../tests/bun-setup.ts",
|
|
11
17
|
"prepublishOnly": "npm run build"
|
|
12
18
|
},
|
|
13
19
|
"keywords": [
|
|
@@ -21,19 +27,28 @@
|
|
|
21
27
|
"license": "MIT",
|
|
22
28
|
"repository": {
|
|
23
29
|
"type": "git",
|
|
24
|
-
"url": "https://github.com/ClayWarren/
|
|
30
|
+
"url": "git+https://github.com/ClayWarren/taskforceai-sdk-typescript.git"
|
|
25
31
|
},
|
|
26
32
|
"bugs": {
|
|
27
|
-
"url": "https://github.com/ClayWarren/
|
|
33
|
+
"url": "https://github.com/ClayWarren/taskforceai-sdk-typescript/issues"
|
|
28
34
|
},
|
|
29
35
|
"homepage": "https://taskforceai.chat",
|
|
30
36
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
37
|
+
"node": ">=24.12.0"
|
|
32
38
|
},
|
|
39
|
+
"dependencies": {},
|
|
33
40
|
"devDependencies": {
|
|
34
|
-
"@
|
|
35
|
-
"
|
|
36
|
-
"
|
|
41
|
+
"@taskforce/shared": "workspace:*",
|
|
42
|
+
"@taskforceai/toolchain": "workspace:*",
|
|
43
|
+
"zod": "catalog:"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"zod": "^4.0.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"zod": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
37
52
|
},
|
|
38
53
|
"files": [
|
|
39
54
|
"dist/**/*",
|
package/dist/index.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/index.test.js
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const vitest_1 = require("vitest");
|
|
4
|
-
const index_1 = require("./index");
|
|
5
|
-
const globalWithFetch = globalThis;
|
|
6
|
-
const originalFetch = globalWithFetch.fetch;
|
|
7
|
-
function createMockResponse(data) {
|
|
8
|
-
return {
|
|
9
|
-
ok: true,
|
|
10
|
-
status: 200,
|
|
11
|
-
json: async () => data,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
(0, vitest_1.afterEach)(() => {
|
|
15
|
-
vitest_1.vi.restoreAllMocks();
|
|
16
|
-
vitest_1.vi.useRealTimers();
|
|
17
|
-
if (originalFetch !== undefined) {
|
|
18
|
-
globalWithFetch.fetch = originalFetch;
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
delete globalWithFetch.fetch;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
(0, vitest_1.describe)('TaskForceAI.makeRequest and helpers', () => {
|
|
25
|
-
(0, vitest_1.it)('includes openRouterKey at the top level of the payload', async () => {
|
|
26
|
-
const fetchMock = vitest_1.vi
|
|
27
|
-
.fn()
|
|
28
|
-
.mockResolvedValue(createMockResponse({ taskId: 'task_123', status: 'processing', message: 'ok' }));
|
|
29
|
-
globalWithFetch.fetch = fetchMock;
|
|
30
|
-
const client = new index_1.TaskForceAI({
|
|
31
|
-
apiKey: 'test-api-key',
|
|
32
|
-
baseUrl: 'https://example.com/api/developer',
|
|
33
|
-
});
|
|
34
|
-
const taskId = await client.submitTask('Analyze data', {
|
|
35
|
-
openRouterKey: 'or-key',
|
|
36
|
-
mock: true,
|
|
37
|
-
});
|
|
38
|
-
(0, vitest_1.expect)(taskId).toBe('task_123');
|
|
39
|
-
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledTimes(1);
|
|
40
|
-
const [url, options] = fetchMock.mock.calls[0];
|
|
41
|
-
(0, vitest_1.expect)(url).toBe('https://example.com/api/developer/run');
|
|
42
|
-
const parsedBody = JSON.parse(options?.body || '{}');
|
|
43
|
-
(0, vitest_1.expect)(parsedBody).toEqual({
|
|
44
|
-
prompt: 'Analyze data',
|
|
45
|
-
options: { mock: true },
|
|
46
|
-
openRouterKey: 'or-key',
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
(0, vitest_1.it)('omits options when only openRouterKey is provided', async () => {
|
|
50
|
-
const fetchMock = vitest_1.vi
|
|
51
|
-
.fn()
|
|
52
|
-
.mockResolvedValue(createMockResponse({ taskId: 'task_456', status: 'processing', message: 'ok' }));
|
|
53
|
-
globalWithFetch.fetch = fetchMock;
|
|
54
|
-
const client = new index_1.TaskForceAI({
|
|
55
|
-
apiKey: 'test-api-key',
|
|
56
|
-
});
|
|
57
|
-
await client.submitTask('Summarize report', {
|
|
58
|
-
openRouterKey: 'or-key',
|
|
59
|
-
});
|
|
60
|
-
(0, vitest_1.expect)(fetchMock).toHaveBeenCalledTimes(1);
|
|
61
|
-
const [, options] = fetchMock.mock.calls[0];
|
|
62
|
-
const parsedBody = JSON.parse(options?.body || '{}');
|
|
63
|
-
(0, vitest_1.expect)(parsedBody).toEqual({
|
|
64
|
-
prompt: 'Summarize report',
|
|
65
|
-
openRouterKey: 'or-key',
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
(0, vitest_1.it)('throws a TaskForceAIError when prompt is invalid', async () => {
|
|
69
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
70
|
-
await (0, vitest_1.expect)(client.submitTask('')).rejects.toThrow('Prompt must be a non-empty string');
|
|
71
|
-
});
|
|
72
|
-
(0, vitest_1.it)('extracts JSON error messages from failed responses', async () => {
|
|
73
|
-
const fetchMock = vitest_1.vi.fn().mockResolvedValue({
|
|
74
|
-
ok: false,
|
|
75
|
-
status: 404,
|
|
76
|
-
text: async () => JSON.stringify({ error: 'Not found' }),
|
|
77
|
-
json: async () => ({}),
|
|
78
|
-
});
|
|
79
|
-
globalWithFetch.fetch = fetchMock;
|
|
80
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key', baseUrl: 'https://example.com/api/developer' });
|
|
81
|
-
await (0, vitest_1.expect)(client.getTaskStatus('missing')).rejects.toThrowError(new index_1.TaskForceAIError('Not found', 404));
|
|
82
|
-
});
|
|
83
|
-
(0, vitest_1.it)('falls back to status code when error payload lacks error field', async () => {
|
|
84
|
-
const fetchMock = vitest_1.vi.fn().mockResolvedValue({
|
|
85
|
-
ok: false,
|
|
86
|
-
status: 500,
|
|
87
|
-
text: async () => JSON.stringify({ message: 'oops' }),
|
|
88
|
-
json: async () => ({}),
|
|
89
|
-
});
|
|
90
|
-
globalWithFetch.fetch = fetchMock;
|
|
91
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key', baseUrl: 'https://example.com/api/developer' });
|
|
92
|
-
await (0, vitest_1.expect)(client.getTaskStatus('task')).rejects.toMatchObject({
|
|
93
|
-
message: 'HTTP 500',
|
|
94
|
-
statusCode: 500,
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
(0, vitest_1.it)('wraps network errors into TaskForceAIError', async () => {
|
|
98
|
-
const fetchMock = vitest_1.vi.fn().mockRejectedValue(new Error('unreachable'));
|
|
99
|
-
globalWithFetch.fetch = fetchMock;
|
|
100
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
101
|
-
await (0, vitest_1.expect)(client.submitTask('prompt')).rejects.toThrow('Network error: unreachable');
|
|
102
|
-
});
|
|
103
|
-
(0, vitest_1.it)('treats non-Error rejections as unknown network errors', async () => {
|
|
104
|
-
const fetchMock = vitest_1.vi.fn().mockRejectedValue('fail');
|
|
105
|
-
globalWithFetch.fetch = fetchMock;
|
|
106
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
107
|
-
await (0, vitest_1.expect)(client.submitTask('prompt')).rejects.toThrow('Network error: Unknown error');
|
|
108
|
-
});
|
|
109
|
-
(0, vitest_1.it)('converts AbortError into timeout error', async () => {
|
|
110
|
-
vitest_1.vi.useFakeTimers();
|
|
111
|
-
const fetchMock = vitest_1.vi.fn((_, init) => {
|
|
112
|
-
const signal = init?.signal;
|
|
113
|
-
return new Promise((_, reject) => {
|
|
114
|
-
signal?.addEventListener('abort', () => {
|
|
115
|
-
const error = new Error('Aborted');
|
|
116
|
-
error.name = 'AbortError';
|
|
117
|
-
reject(error);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
globalWithFetch.fetch = fetchMock;
|
|
122
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key', timeout: 100 });
|
|
123
|
-
const promise = client.getTaskStatus('slow-task');
|
|
124
|
-
const expectation = (0, vitest_1.expect)(promise).rejects.toThrow('Request timeout');
|
|
125
|
-
await vitest_1.vi.advanceTimersByTimeAsync(100);
|
|
126
|
-
await expectation;
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
(0, vitest_1.describe)('TaskForceAI task helpers', () => {
|
|
130
|
-
(0, vitest_1.it)('validates task identifiers for status and result lookups', async () => {
|
|
131
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
132
|
-
await (0, vitest_1.expect)(client.getTaskStatus('')).rejects.toThrow('Task ID must be a non-empty string');
|
|
133
|
-
await (0, vitest_1.expect)(client.getTaskResult('')).rejects.toThrow('Task ID must be a non-empty string');
|
|
134
|
-
});
|
|
135
|
-
(0, vitest_1.it)('fetches task status and result through makeRequest', async () => {
|
|
136
|
-
const fetchMock = vitest_1.vi.fn().mockResolvedValue({
|
|
137
|
-
ok: true,
|
|
138
|
-
status: 200,
|
|
139
|
-
json: async () => ({ taskId: 'task', status: 'completed', result: 'done' }),
|
|
140
|
-
});
|
|
141
|
-
globalWithFetch.fetch = fetchMock;
|
|
142
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key', baseUrl: 'https://example.com/api/developer' });
|
|
143
|
-
const status = await client.getTaskStatus('task');
|
|
144
|
-
(0, vitest_1.expect)(status.status).toBe('completed');
|
|
145
|
-
fetchMock.mockResolvedValueOnce({
|
|
146
|
-
ok: true,
|
|
147
|
-
status: 200,
|
|
148
|
-
json: async () => ({ taskId: 'task', result: 'done' }),
|
|
149
|
-
});
|
|
150
|
-
const result = await client.getTaskResult('task');
|
|
151
|
-
(0, vitest_1.expect)(result.result).toBe('done');
|
|
152
|
-
});
|
|
153
|
-
(0, vitest_1.it)('waits for completion successfully', async () => {
|
|
154
|
-
vitest_1.vi.useFakeTimers();
|
|
155
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
156
|
-
const statuses = [
|
|
157
|
-
{ taskId: 'task', status: 'processing' },
|
|
158
|
-
{ taskId: 'task', status: 'completed', result: 'done' },
|
|
159
|
-
];
|
|
160
|
-
const statusSpy = vitest_1.vi
|
|
161
|
-
.spyOn(client, 'getTaskStatus')
|
|
162
|
-
.mockImplementation(async () => statuses.shift());
|
|
163
|
-
const promise = client.waitForCompletion('task', 50, 5);
|
|
164
|
-
await vitest_1.vi.advanceTimersByTimeAsync(50);
|
|
165
|
-
const finalResult = await promise;
|
|
166
|
-
(0, vitest_1.expect)(finalResult).toEqual({ taskId: 'task', result: 'done' });
|
|
167
|
-
(0, vitest_1.expect)(statusSpy).toHaveBeenCalledTimes(2);
|
|
168
|
-
});
|
|
169
|
-
(0, vitest_1.it)('fails when task reports an error status', async () => {
|
|
170
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
171
|
-
vitest_1.vi.spyOn(client, 'getTaskStatus').mockResolvedValue({
|
|
172
|
-
taskId: 'task',
|
|
173
|
-
status: 'failed',
|
|
174
|
-
error: 'boom',
|
|
175
|
-
});
|
|
176
|
-
await (0, vitest_1.expect)(client.waitForCompletion('task')).rejects.toThrow('boom');
|
|
177
|
-
});
|
|
178
|
-
(0, vitest_1.it)('uses default failure message when status error is missing', async () => {
|
|
179
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
180
|
-
vitest_1.vi.spyOn(client, 'getTaskStatus').mockResolvedValue({
|
|
181
|
-
taskId: 'task',
|
|
182
|
-
status: 'failed',
|
|
183
|
-
});
|
|
184
|
-
await (0, vitest_1.expect)(client.waitForCompletion('task')).rejects.toThrow('Task failed');
|
|
185
|
-
});
|
|
186
|
-
(0, vitest_1.it)('fails when task never completes within max attempts', async () => {
|
|
187
|
-
vitest_1.vi.useFakeTimers();
|
|
188
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
189
|
-
vitest_1.vi.spyOn(client, 'getTaskStatus').mockResolvedValue({
|
|
190
|
-
taskId: 'task',
|
|
191
|
-
status: 'processing',
|
|
192
|
-
});
|
|
193
|
-
const promise = client.waitForCompletion('task', 20, 2);
|
|
194
|
-
const expectation = (0, vitest_1.expect)(promise).rejects.toThrow('Task did not complete within the expected time');
|
|
195
|
-
await vitest_1.vi.advanceTimersByTimeAsync(20);
|
|
196
|
-
await vitest_1.vi.advanceTimersByTimeAsync(20);
|
|
197
|
-
await expectation;
|
|
198
|
-
});
|
|
199
|
-
(0, vitest_1.it)('chains runTask through submitTask and waitForCompletion', async () => {
|
|
200
|
-
const client = new index_1.TaskForceAI({ apiKey: 'key' });
|
|
201
|
-
const submitSpy = vitest_1.vi.spyOn(client, 'submitTask').mockResolvedValue('task-123');
|
|
202
|
-
const waitSpy = vitest_1.vi.spyOn(client, 'waitForCompletion').mockResolvedValue({
|
|
203
|
-
taskId: 'task-123',
|
|
204
|
-
result: 'ok',
|
|
205
|
-
});
|
|
206
|
-
const result = await client.runTask('prompt', { mock: true }, 10, 2);
|
|
207
|
-
(0, vitest_1.expect)(result).toEqual({ taskId: 'task-123', result: 'ok' });
|
|
208
|
-
(0, vitest_1.expect)(submitSpy).toHaveBeenCalledWith('prompt', { mock: true });
|
|
209
|
-
(0, vitest_1.expect)(waitSpy).toHaveBeenCalledWith('task-123', 10, 2);
|
|
210
|
-
});
|
|
211
|
-
});
|