taskforceai-sdk 1.2.0 → 1.2.1

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 CHANGED
@@ -22,19 +22,19 @@ 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 OpenRouter key for premium models
25
+ // Or submit with your own Vercel AI Gateway key for premium models
26
26
  const taskId2 = await client.submitTask('Complex analysis', {
27
- openRouterKey: 'your-openrouter-key-here',
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
- console.log(result.result);
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
- console.log(status.status, status.result);
37
+ // handle incremental status updates
38
38
  }
39
39
  ```
40
40
 
@@ -75,7 +75,7 @@ async submitTask(
75
75
  - `prompt`: The user's input prompt
76
76
  - `options.silent`: Suppress logging (default: false)
77
77
  - `options.mock`: Use mock responses for testing (default: false)
78
- - `options.openRouterKey`: Supply your own OpenRouter API key to pick premium models
78
+ - `options.vercelAiKey`: Supply your own Vercel AI Gateway API key to pick premium models
79
79
  - `options.*`: Any additional TaskForceAI orchestration flags are forwarded untouched
80
80
 
81
81
  **Returns:** Task ID string
@@ -155,7 +155,7 @@ try {
155
155
  const result = await client.runTask('Your prompt');
156
156
  } catch (error) {
157
157
  if (error instanceof TaskForceAIError) {
158
- console.error(`API Error: ${error.message} (Status: ${error.statusCode})`);
158
+ // handle API error, inspect error.statusCode and error.details
159
159
  }
160
160
  }
161
161
  ```
package/dist/index.d.ts CHANGED
@@ -1,98 +1,21 @@
1
- /**
2
- * TaskForceAI SDK version
3
- */
4
- export declare const VERSION = "1.1.0";
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
+ import { TaskForceAIError, transportDefaults as def } from './transport';
2
+ import type { TaskForceAIOptions, TaskResult, TaskStatus, TaskStatusCallback, TaskStatusStream, TaskSubmissionOptions } from './types';
3
+ import { VERSION } from './types';
46
4
  export declare class TaskForceAI {
47
- private apiKey;
48
- private baseUrl;
49
- private timeout;
50
- private responseHook?;
51
- constructor(options: TaskForceAIOptions);
52
- private makeRequest;
53
- /**
54
- * Submit a task for multi-agent orchestration.
55
- *
56
- * @param prompt - The user's input prompt
57
- * @param options - Optional settings for the task
58
- * @returns Promise<string> - The task ID
59
- */
60
- submitTask(prompt: string, options?: TaskSubmissionOptions): Promise<string>;
61
- /**
62
- * Get the status of a submitted task.
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>;
5
+ private ak;
6
+ private url;
7
+ private t;
8
+ private rh?;
9
+ constructor(o: TaskForceAIOptions);
10
+ private req;
11
+ submitTask(p: string, o?: TaskSubmissionOptions): Promise<string>;
12
+ getTaskStatus(id: any): Promise<TaskStatus>;
13
+ getTaskResult(id: any): Promise<TaskResult>;
14
+ private poll;
15
+ waitForCompletion(id: string, ms?: 2000, max?: 150, on?: TaskStatusCallback, sig?: AbortSignal): Promise<TaskResult>;
16
+ runTask(p: string, o?: TaskSubmissionOptions, ms?: 2000, max?: 150, on?: TaskStatusCallback): Promise<TaskResult>;
17
+ streamTaskStatus(id: any, ms?: 2000, max?: 150, on?: TaskStatusCallback, sig?: AbortSignal): TaskStatusStream;
18
+ runTaskStream(p: string, o?: TaskSubmissionOptions, ms?: 2000, max?: 150, on?: TaskStatusCallback, sig?: AbortSignal): Promise<TaskStatusStream>;
97
19
  }
20
+ export { TaskForceAIError, TaskStatus, TaskResult, TaskSubmissionOptions, TaskStatusCallback, TaskStatusStream, TaskForceAIOptions, VERSION, def as transportDefaults, };
98
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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;AAOlC,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,CAAC,CAAS;IAClB,OAAO,CAAC,EAAE,CAAC,CAAwB;gBACvB,CAAC,EAAE,kBAAkB;IAOjC,OAAO,CAAC,GAAG,CAOP;IAEE,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,86 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TaskForceAI = exports.TaskForceAIError = exports.TaskStatusStream = exports.VERSION = void 0;
4
- /**
5
- * TaskForceAI SDK version
6
- */
7
- exports.VERSION = '1.1.0';
8
- class TaskStatusStream {
9
- constructor(client, taskId, pollInterval, maxAttempts, onStatus) {
10
- this.client = client;
11
- this.taskId = taskId;
12
- this.pollInterval = pollInterval;
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
+ });
45
12
  class TaskForceAI {
46
- constructor(options) {
47
- this.apiKey = options.apiKey;
48
- this.baseUrl = options.baseUrl || 'https://taskforceai.chat/api/developer';
49
- this.timeout = options.timeout || 30000; // 30 seconds
50
- this.responseHook = options.responseHook;
13
+ constructor(o) {
14
+ this.req = (e, i = {}, r = false) => (0, transport_1.makeRequest)(e, i, { apiKey: this.ak, baseUrl: this.url, timeout: this.t, responseHook: this.rh }, r, transport_1.transportDefaults.maxRetries);
15
+ this.ak = o.apiKey;
16
+ this.url = o.baseUrl || 'https://taskforceai.chat/api/developer';
17
+ this.t = o.timeout || transport_1.transportDefaults.timeout;
18
+ this.rh = o.responseHook;
51
19
  }
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);
71
- }
72
- if (!response.ok) {
73
- let errorMessage = `HTTP ${response.status}`;
74
- try {
75
- const errorText = await response.text();
76
- try {
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'}`);
103
- }
20
+ async submitTask(p, o = {}) {
21
+ if (typeof p !== 'string' || !p.trim())
22
+ throw new transport_1.TaskForceAIError('Prompt must be a non-empty string');
23
+ const { vercelAiKey: v, silent: s = false, mock: m = false, ...rest } = o;
24
+ const body = { prompt: p, options: { silent: s, mock: m, ...rest } };
25
+ if (v)
26
+ body.vercelAiKey = v;
27
+ return (await this.req('/run', { method: 'POST', body: JSON.stringify(body) })).taskId;
104
28
  }
105
- /**
106
- * Submit a task for multi-agent orchestration.
107
- *
108
- * @param prompt - The user's input prompt
109
- * @param options - Optional settings for the task
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');
115
- }
116
- const { openRouterKey, silent, mock, ...optionFlags } = options;
117
- const payload = { prompt };
118
- const normalizedOptions = {
119
- silent: typeof silent === 'boolean' ? silent : false,
120
- mock: typeof mock === 'boolean' ? mock : false,
121
- ...optionFlags,
122
- };
123
- if (Object.keys(normalizedOptions).length > 0) {
124
- payload.options = normalizedOptions;
125
- }
126
- if (openRouterKey) {
127
- payload.openRouterKey = openRouterKey;
128
- }
129
- const response = await this.makeRequest('/run', {
130
- method: 'POST',
131
- body: JSON.stringify(payload),
132
- });
133
- return response.taskId;
29
+ async getTaskStatus(id) {
30
+ if (!id || typeof id !== 'string')
31
+ throw new transport_1.TaskForceAIError('Task ID must be a non-empty string');
32
+ return this.req(`/status/${id}`, {}, true);
134
33
  }
135
- /**
136
- * Get the status of a submitted task.
137
- *
138
- * @param taskId - The task ID returned from submitTask
139
- * @returns Promise<TaskStatus> - The current task status
140
- */
141
- async getTaskStatus(taskId) {
142
- if (!taskId || typeof taskId !== 'string') {
143
- throw new TaskForceAIError('Task ID must be a non-empty string');
144
- }
145
- return this.makeRequest(`/status/${taskId}`);
34
+ async getTaskResult(id) {
35
+ if (!id || typeof id !== 'string')
36
+ throw new transport_1.TaskForceAIError('Task ID must be a non-empty string');
37
+ return this.req(`/results/${id}`);
146
38
  }
147
- /**
148
- * Get the final result of a completed task.
149
- *
150
- * @param taskId - The task ID returned from submitTask
151
- * @returns Promise<TaskResult> - The task result
152
- */
153
- async getTaskResult(taskId) {
154
- if (!taskId || typeof taskId !== 'string') {
155
- throw new TaskForceAIError('Task ID must be a non-empty string');
39
+ async *poll(id, ms, max, on, sig) {
40
+ for (let i = 0; i < max; i++) {
41
+ if (sig?.aborted)
42
+ throw new transport_1.TaskForceAIError('Task polling cancelled');
43
+ // eslint-disable-next-line no-await-in-loop
44
+ const s = await this.getTaskStatus(id);
45
+ on?.(s);
46
+ yield s;
47
+ if (['completed', 'failed'].includes(s.status))
48
+ return;
49
+ // eslint-disable-next-line no-await-in-loop
50
+ await sleep(ms);
156
51
  }
157
- return this.makeRequest(`/results/${taskId}`);
52
+ throw new transport_1.TaskForceAIError('Task did not complete within the expected time');
158
53
  }
159
- /**
160
- * Wait for a task to complete and return the result.
161
- * This method polls the status until the task is completed or failed.
162
- *
163
- * @param taskId - The task ID returned from submitTask
164
- * @param pollInterval - Interval between status checks in milliseconds (default: 2000)
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));
54
+ async waitForCompletion(id, ms = transport_1.transportDefaults.pollIntervalMs, max = transport_1.transportDefaults.maxPollAttempts, on, sig) {
55
+ for await (const s of this.poll(id, ms, max, on, sig)) {
56
+ if (s.status === 'completed' && s.result)
57
+ return s;
58
+ if (s.status === 'failed')
59
+ throw new transport_1.TaskForceAIError(s.error || 'Task failed');
180
60
  }
181
- throw new TaskForceAIError('Task did not complete within the expected time');
61
+ throw new transport_1.TaskForceAIError('Task did not complete within the expected time');
182
62
  }
183
- /**
184
- * Submit a task and wait for completion in one call.
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);
63
+ async runTask(p, o = {}, ms = transport_1.transportDefaults.pollIntervalMs, max = transport_1.transportDefaults.maxPollAttempts, on) {
64
+ return this.waitForCompletion(await this.submitTask(p, o), ms, max, on);
195
65
  }
196
- streamTaskStatus(taskId, pollInterval = 2000, maxAttempts = 150, onStatus) {
197
- if (!taskId || typeof taskId !== 'string') {
198
- throw new TaskForceAIError('Task ID must be a non-empty string');
199
- }
200
- return new TaskStatusStream(this, taskId, pollInterval, maxAttempts, onStatus);
66
+ streamTaskStatus(id, ms = transport_1.transportDefaults.pollIntervalMs, max = transport_1.transportDefaults.maxPollAttempts, on, sig) {
67
+ if (!id || typeof id !== 'string')
68
+ throw new transport_1.TaskForceAIError('Task ID must be a non-empty string');
69
+ let cancel = false;
70
+ return {
71
+ taskId: id,
72
+ cancel: () => (cancel = true),
73
+ [Symbol.asyncIterator]: async function* () {
74
+ for await (const s of this.poll(id, ms, max, on, sig)) {
75
+ if (cancel)
76
+ throw new transport_1.TaskForceAIError('Task stream cancelled');
77
+ yield s;
78
+ }
79
+ }.bind(this),
80
+ };
201
81
  }
202
- async runTaskStream(prompt, options = {}, pollInterval = 2000, maxAttempts = 150, onStatus) {
203
- const taskId = await this.submitTask(prompt, options);
204
- return new TaskStatusStream(this, taskId, pollInterval, maxAttempts, onStatus);
82
+ async runTaskStream(p, o = {}, ms = transport_1.transportDefaults.pollIntervalMs, max = transport_1.transportDefaults.maxPollAttempts, on, sig) {
83
+ return this.streamTaskStatus(await this.submitTask(p, o), ms, max, on, sig);
205
84
  }
206
85
  }
207
86
  exports.TaskForceAI = TaskForceAI;
@@ -0,0 +1,19 @@
1
+ export declare class TaskForceAIError extends Error {
2
+ statusCode?: number | undefined;
3
+ constructor(message: string, statusCode?: number | undefined);
4
+ }
5
+ export interface TransportConfig {
6
+ apiKey: string;
7
+ baseUrl: string;
8
+ timeout: number;
9
+ responseHook?: (response: Response) => void;
10
+ }
11
+ export declare const makeRequest: <T>(endpoint: string, options: RequestInit, { apiKey, baseUrl, timeout, responseHook }: TransportConfig, retryable?: boolean, maxRetries?: number) => Promise<T>;
12
+ export declare const transportDefaults: {
13
+ readonly timeout: 30000;
14
+ readonly maxRetries: 3;
15
+ readonly backoffMs: 500;
16
+ readonly pollIntervalMs: 2000;
17
+ readonly maxPollAttempts: 150;
18
+ };
19
+ //# 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,EAAE,MAAM;gBAD1B,OAAO,EAAE,MAAM,EACR,UAAU,CAAC,EAAE,MAAM,YAAA;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,GAAU,CAAC,EACjC,UAAU,MAAM,EAChB,SAAS,WAAW,EACpB,4CAA4C,eAAe,EAC3D,mBAAiB,EACjB,mBAAgC,KAC/B,OAAO,CAAC,CAAC,CAyDX,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
+ };
@@ -0,0 +1,32 @@
1
+ export declare const VERSION = "1.2.1";
2
+ export interface TaskForceAIOptions {
3
+ apiKey: string;
4
+ baseUrl?: string;
5
+ timeout?: number;
6
+ responseHook?: TaskResponseHook;
7
+ }
8
+ export type TaskSubmissionOptions = {
9
+ [key: string]: unknown;
10
+ silent?: boolean;
11
+ mock?: boolean;
12
+ vercelAiKey?: string;
13
+ };
14
+ export interface TaskStatus {
15
+ taskId: string;
16
+ status: 'processing' | 'completed' | 'failed';
17
+ result?: string;
18
+ error?: string;
19
+ warnings?: string[];
20
+ metadata?: Record<string, unknown>;
21
+ }
22
+ export interface TaskResult extends TaskStatus {
23
+ status: 'completed';
24
+ result: string;
25
+ }
26
+ export type TaskStatusCallback = (status: TaskStatus) => void;
27
+ export type TaskResponseHook = (response: Response) => void;
28
+ export interface TaskStatusStream extends AsyncIterable<TaskStatus> {
29
+ taskId: string;
30
+ cancel(): void;
31
+ }
32
+ //# 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,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;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
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ exports.VERSION = '1.2.1';
package/package.json CHANGED
@@ -1,13 +1,19 @@
1
1
  {
2
2
  "name": "taskforceai-sdk",
3
- "version": "1.2.0",
4
- "description": "Official SDK for TaskForceAI multi-agent orchestration API",
3
+ "version": "1.2.1",
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": "vitest run",
10
- "test:watch": "vitest",
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/grok4fastheavy"
30
+ "url": "git+https://github.com/ClayWarren/grok4fastheavy.git"
25
31
  },
26
32
  "bugs": {
27
33
  "url": "https://github.com/ClayWarren/grok4fastheavy/issues"
28
34
  },
29
35
  "homepage": "https://taskforceai.chat",
30
36
  "engines": {
31
- "node": ">=18.0.0"
37
+ "node": ">=24.12.0"
32
38
  },
39
+ "dependencies": {},
33
40
  "devDependencies": {
34
- "@vitest/coverage-v8": "4.0.7",
35
- "typescript": "^5.9.3",
36
- "vitest": "^4.0.7"
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/**/*",
@@ -1 +0,0 @@
1
- export {};
@@ -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
- });