@jordanalec/dtk 1.0.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +730 -0
  3. package/dist/add.js +89 -0
  4. package/dist/cli.js +12 -0
  5. package/dist/init.js +20 -0
  6. package/dist/utils/patch.js +8 -0
  7. package/package.json +52 -0
  8. package/templates/init/.env.template +2 -0
  9. package/templates/init/GUIDE.md +543 -0
  10. package/templates/init/README.md +59 -0
  11. package/templates/init/jest.config.ts +19 -0
  12. package/templates/init/package.json +22 -0
  13. package/templates/init/src/lib/auth.test.ts +48 -0
  14. package/templates/init/src/lib/basic-auth.ts +6 -0
  15. package/templates/init/src/lib/bearer-token.ts +5 -0
  16. package/templates/init/src/lib/http.test.ts +197 -0
  17. package/templates/init/src/lib/http.ts +81 -0
  18. package/templates/init/src/lib/oauth.test.ts +61 -0
  19. package/templates/init/src/lib/oauth.ts +15 -0
  20. package/templates/init/src/lib/token.ts +5 -0
  21. package/templates/init/src/load-env.ts +4 -0
  22. package/templates/init/src/runbooks/example.ts +33 -0
  23. package/templates/init/src/suite.test.ts +94 -0
  24. package/templates/init/src/suite.ts +70 -0
  25. package/templates/init/src/types/http.ts +12 -0
  26. package/templates/init/src/types/oauth.ts +13 -0
  27. package/templates/init/src/types/suite.ts +37 -0
  28. package/templates/init/tsconfig.json +14 -0
  29. package/templates/init/tsconfig.test.json +8 -0
  30. package/templates/plugins/aws-dynamo/env.txt +2 -0
  31. package/templates/plugins/aws-dynamo/example.ts +75 -0
  32. package/templates/plugins/aws-dynamo/plugin.json +38 -0
  33. package/templates/plugins/aws-dynamo/service.test.ts +180 -0
  34. package/templates/plugins/aws-dynamo/service.ts +73 -0
  35. package/templates/plugins/aws-dynamo/types.ts +29 -0
  36. package/templates/plugins/aws-s3/env.txt +2 -0
  37. package/templates/plugins/aws-s3/example.ts +41 -0
  38. package/templates/plugins/aws-s3/plugin.json +38 -0
  39. package/templates/plugins/aws-s3/service.test.ts +150 -0
  40. package/templates/plugins/aws-s3/service.ts +43 -0
  41. package/templates/plugins/aws-s3/types.ts +28 -0
  42. package/templates/plugins/aws-sns/env.txt +2 -0
  43. package/templates/plugins/aws-sns/example.ts +18 -0
  44. package/templates/plugins/aws-sns/plugin.json +37 -0
  45. package/templates/plugins/aws-sns/service.test.ts +79 -0
  46. package/templates/plugins/aws-sns/service.ts +28 -0
  47. package/templates/plugins/aws-sns/types.ts +8 -0
  48. package/templates/plugins/aws-sqs/env.txt +2 -0
  49. package/templates/plugins/aws-sqs/example.ts +16 -0
  50. package/templates/plugins/aws-sqs/plugin.json +37 -0
  51. package/templates/plugins/aws-sqs/service.test.ts +63 -0
  52. package/templates/plugins/aws-sqs/service.ts +27 -0
  53. package/templates/plugins/aws-sqs/types.ts +8 -0
  54. package/templates/plugins/open-ai/env.txt +1 -0
  55. package/templates/plugins/open-ai/example.ts +27 -0
  56. package/templates/plugins/open-ai/plugin.json +36 -0
  57. package/templates/plugins/open-ai/service.test.ts +55 -0
  58. package/templates/plugins/open-ai/service.ts +26 -0
  59. package/templates/plugins/open-ai/types.ts +61 -0
  60. package/templates/plugins/tsconfig.json +11 -0
@@ -0,0 +1,28 @@
1
+ export interface S3Config {
2
+ region: string;
3
+ }
4
+
5
+ export interface UploadOptions {
6
+ contentType?: string;
7
+ metadata?: Record<string, string>;
8
+ }
9
+
10
+ export interface UploadFileResult {
11
+ bucket: string;
12
+ key: string;
13
+ etag: string | null;
14
+ }
15
+
16
+ export interface DownloadFileResult {
17
+ bucket: string;
18
+ key: string;
19
+ localPath: string;
20
+ contentType: string | undefined;
21
+ }
22
+
23
+ export interface PresignedUrlResult {
24
+ url: string;
25
+ expiresIn: number;
26
+ bucket: string;
27
+ key: string;
28
+ }
@@ -0,0 +1,2 @@
1
+ SNS_TOPIC_ARN=
2
+ AWS_REGION=
@@ -0,0 +1,18 @@
1
+ import "../load-env.js";
2
+ import { suite } from "../suite.js";
3
+
4
+ await suite()
5
+ .sns({
6
+ topicArn: process.env.SNS_TOPIC_ARN!,
7
+ region: process.env.AWS_REGION!,
8
+ })
9
+ .step("publish-message", async (ctx) => {
10
+ const result = await ctx.services.sns.publish(
11
+ "Hello from dtk",
12
+ "dtk example notification",
13
+ { source: "dtk-example" }
14
+ );
15
+ console.log("messageId:", result.messageId);
16
+ return result;
17
+ })
18
+ .run("throwOnError");
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "aws-sns",
3
+ "description": "AWS SNS -- publish messages to a topic",
4
+ "dependencies": {
5
+ "@aws-sdk/client-sns": "^3.300.0"
6
+ },
7
+ "files": [
8
+ { "src": "service.ts", "dest": "src/services/sns.ts" },
9
+ { "src": "types.ts", "dest": "src/types/aws-sns.ts" },
10
+ { "src": "service.test.ts", "dest": "src/services/sns.test.ts" }
11
+ ],
12
+ "env": "env.txt",
13
+ "example": "example.ts",
14
+ "transforms": {
15
+ "service.ts": [
16
+ { "from": "./types.js", "to": "../types/aws-sns.js" }
17
+ ],
18
+ "service.test.ts": [
19
+ { "from": "./service.js", "to": "./sns.js" }
20
+ ]
21
+ },
22
+ "patches": {
23
+ "src/suite.ts": {
24
+ "imports": [
25
+ "import { createSnsService } from \"./services/sns.js\";",
26
+ "import type { SnsConfig } from \"./types/aws-sns.js\";"
27
+ ],
28
+ "configs": " private snsConfig?: SnsConfig;",
29
+ "methods": " sns(config: SnsConfig): this { this.snsConfig = config; return this; }",
30
+ "services": " sns: createSnsService(this.snsConfig),"
31
+ },
32
+ "src/types/suite.ts": {
33
+ "type-imports": "import type { SnsConfig, PublishResult } from \"./aws-sns.js\";",
34
+ "service-types": " sns: { publish(message: string, subject?: string, attributes?: Record<string, string>): Promise<PublishResult>; };"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,79 @@
1
+ import { createSnsService } from './service.js';
2
+
3
+ jest.mock('@aws-sdk/client-sns');
4
+ import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
5
+
6
+ const mockSend = jest.fn();
7
+
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ (SNSClient as jest.Mock).mockImplementation(() => ({ send: mockSend }));
11
+ });
12
+
13
+ describe('createSnsService', () => {
14
+ const config = { topicArn: 'arn:aws:sns:us-east-1:123:my-topic', region: 'us-east-1' };
15
+
16
+ it('returns the messageId from the SNS response', async () => {
17
+ mockSend.mockResolvedValue({ MessageId: 'pub-abc-123' });
18
+ const sns = createSnsService(config);
19
+ const result = await sns.publish('hello');
20
+ expect(result).toEqual({ messageId: 'pub-abc-123' });
21
+ });
22
+
23
+ it('returns null messageId when not present in the response', async () => {
24
+ mockSend.mockResolvedValue({});
25
+ const sns = createSnsService(config);
26
+ const result = await sns.publish('hello');
27
+ expect(result.messageId).toBeNull();
28
+ });
29
+
30
+ it('sends the message and topic ARN in the command', async () => {
31
+ mockSend.mockResolvedValue({ MessageId: 'id' });
32
+ const sns = createSnsService(config);
33
+ await sns.publish('my notification');
34
+ const commandArg = (PublishCommand as unknown as jest.Mock).mock.calls[0][0];
35
+ expect(commandArg.Message).toBe('my notification');
36
+ expect(commandArg.TopicArn).toBe(config.topicArn);
37
+ });
38
+
39
+ it('includes Subject in the command when provided', async () => {
40
+ mockSend.mockResolvedValue({ MessageId: 'id' });
41
+ const sns = createSnsService(config);
42
+ await sns.publish('body', 'Alert: something happened');
43
+ const commandArg = (PublishCommand as unknown as jest.Mock).mock.calls[0][0];
44
+ expect(commandArg.Subject).toBe('Alert: something happened');
45
+ });
46
+
47
+ it('omits Subject when not provided', async () => {
48
+ mockSend.mockResolvedValue({ MessageId: 'id' });
49
+ const sns = createSnsService(config);
50
+ await sns.publish('body');
51
+ const commandArg = (PublishCommand as unknown as jest.Mock).mock.calls[0][0];
52
+ expect(commandArg.Subject).toBeUndefined();
53
+ });
54
+
55
+ it('maps string attributes to the SNS MessageAttribute format', async () => {
56
+ mockSend.mockResolvedValue({ MessageId: 'id' });
57
+ const sns = createSnsService(config);
58
+ await sns.publish('body', undefined, { source: 'my-runbook', tier: 'prod' });
59
+ const commandArg = (PublishCommand as unknown as jest.Mock).mock.calls[0][0];
60
+ expect(commandArg.MessageAttributes).toEqual({
61
+ source: { DataType: 'String', StringValue: 'my-runbook' },
62
+ tier: { DataType: 'String', StringValue: 'prod' },
63
+ });
64
+ });
65
+
66
+ it('omits MessageAttributes when none are provided', async () => {
67
+ mockSend.mockResolvedValue({ MessageId: 'id' });
68
+ const sns = createSnsService(config);
69
+ await sns.publish('body');
70
+ const commandArg = (PublishCommand as unknown as jest.Mock).mock.calls[0][0];
71
+ expect(commandArg.MessageAttributes).toBeUndefined();
72
+ });
73
+
74
+ it('throws error when publish is called without config', async () => {
75
+ const sns = createSnsService();
76
+ await expect(sns.publish('hello')).rejects.toThrow('sns service is not configured');
77
+ });
78
+
79
+ });
@@ -0,0 +1,28 @@
1
+ import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
2
+ import type { SnsConfig, PublishResult } from "./types.js";
3
+
4
+ export function createSnsService(config?: SnsConfig) {
5
+ const ensureConfig = () => {
6
+ if (!config) throw new Error("sns service is not configured -- call .sns(config) on the suite");
7
+ };
8
+ const client = config ? new SNSClient({ region: config.region }) : null;
9
+
10
+ return {
11
+ publish: async (message: string, subject?: string, attributes?: Record<string, string>): Promise<PublishResult> => {
12
+ ensureConfig();
13
+ const response = await client!.send(
14
+ new PublishCommand({
15
+ TopicArn: config!.topicArn,
16
+ Message: message,
17
+ ...(subject && { Subject: subject }),
18
+ ...(attributes && {
19
+ MessageAttributes: Object.fromEntries(
20
+ Object.entries(attributes).map(([k, v]) => [k, { DataType: "String", StringValue: v }])
21
+ ),
22
+ }),
23
+ })
24
+ );
25
+ return { messageId: response.MessageId ?? null };
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,8 @@
1
+ export interface SnsConfig {
2
+ topicArn: string;
3
+ region: string;
4
+ }
5
+
6
+ export interface PublishResult {
7
+ messageId: string | null;
8
+ }
@@ -0,0 +1,2 @@
1
+ SQS_QUEUE_URL=
2
+ AWS_REGION=
@@ -0,0 +1,16 @@
1
+ import "../load-env.js";
2
+ import { suite } from "../suite.js";
3
+
4
+ await suite()
5
+ .sqs({
6
+ queueUrl: process.env.SQS_QUEUE_URL!,
7
+ region: process.env.AWS_REGION!,
8
+ })
9
+ .step("send-message", async (ctx) => {
10
+ const result = await ctx.services.sqs.sendMessage("Hello from dtk", {
11
+ source: "dtk-example",
12
+ });
13
+ console.log("messageId:", result.messageId);
14
+ return result;
15
+ })
16
+ .run("throwOnError");
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "aws-sqs",
3
+ "description": "AWS SQS -- send messages to a queue",
4
+ "dependencies": {
5
+ "@aws-sdk/client-sqs": "^3.300.0"
6
+ },
7
+ "files": [
8
+ { "src": "service.ts", "dest": "src/services/sqs.ts" },
9
+ { "src": "types.ts", "dest": "src/types/aws-sqs.ts" },
10
+ { "src": "service.test.ts", "dest": "src/services/sqs.test.ts" }
11
+ ],
12
+ "env": "env.txt",
13
+ "example": "example.ts",
14
+ "transforms": {
15
+ "service.ts": [
16
+ { "from": "./types.js", "to": "../types/aws-sqs.js" }
17
+ ],
18
+ "service.test.ts": [
19
+ { "from": "./service.js", "to": "./sqs.js" }
20
+ ]
21
+ },
22
+ "patches": {
23
+ "src/suite.ts": {
24
+ "imports": [
25
+ "import { createSqsService } from \"./services/sqs.js\";",
26
+ "import type { SqsConfig } from \"./types/aws-sqs.js\";"
27
+ ],
28
+ "configs": " private sqsConfig?: SqsConfig;",
29
+ "methods": " sqs(config: SqsConfig): this { this.sqsConfig = config; return this; }",
30
+ "services": " sqs: createSqsService(this.sqsConfig),"
31
+ },
32
+ "src/types/suite.ts": {
33
+ "type-imports": "import type { SqsConfig, SendMessageResult } from \"./aws-sqs.js\";",
34
+ "service-types": " sqs: { sendMessage(body: string, attributes?: Record<string, string>): Promise<SendMessageResult>; };"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,63 @@
1
+ import { createSqsService } from './service.js';
2
+
3
+ jest.mock('@aws-sdk/client-sqs');
4
+ import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
5
+
6
+ const mockSend = jest.fn();
7
+
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ (SQSClient as jest.Mock).mockImplementation(() => ({ send: mockSend }));
11
+ });
12
+
13
+ describe('createSqsService', () => {
14
+ const config = { queueUrl: 'https://sqs.us-east-1.amazonaws.com/123/my-queue', region: 'us-east-1' };
15
+
16
+ it('returns the messageId from the SQS response', async () => {
17
+ mockSend.mockResolvedValue({ MessageId: 'msg-abc-123' });
18
+ const sqs = createSqsService(config);
19
+ const result = await sqs.sendMessage('hello');
20
+ expect(result).toEqual({ messageId: 'msg-abc-123' });
21
+ });
22
+
23
+ it('returns null messageId when not present in the response', async () => {
24
+ mockSend.mockResolvedValue({});
25
+ const sqs = createSqsService(config);
26
+ const result = await sqs.sendMessage('hello');
27
+ expect(result.messageId).toBeNull();
28
+ });
29
+
30
+ it('sends the message body and queue URL in the command', async () => {
31
+ mockSend.mockResolvedValue({ MessageId: 'id' });
32
+ const sqs = createSqsService(config);
33
+ await sqs.sendMessage('my message body');
34
+ const commandArg = (SendMessageCommand as unknown as jest.Mock).mock.calls[0][0];
35
+ expect(commandArg.MessageBody).toBe('my message body');
36
+ expect(commandArg.QueueUrl).toBe(config.queueUrl);
37
+ });
38
+
39
+ it('maps string attributes to the SQS MessageAttribute format', async () => {
40
+ mockSend.mockResolvedValue({ MessageId: 'id' });
41
+ const sqs = createSqsService(config);
42
+ await sqs.sendMessage('body', { source: 'my-runbook', env: 'test' });
43
+ const commandArg = (SendMessageCommand as unknown as jest.Mock).mock.calls[0][0];
44
+ expect(commandArg.MessageAttributes).toEqual({
45
+ source: { DataType: 'String', StringValue: 'my-runbook' },
46
+ env: { DataType: 'String', StringValue: 'test' },
47
+ });
48
+ });
49
+
50
+ it('omits MessageAttributes when none are provided', async () => {
51
+ mockSend.mockResolvedValue({ MessageId: 'id' });
52
+ const sqs = createSqsService(config);
53
+ await sqs.sendMessage('body');
54
+ const commandArg = (SendMessageCommand as unknown as jest.Mock).mock.calls[0][0];
55
+ expect(commandArg.MessageAttributes).toBeUndefined();
56
+ });
57
+
58
+ it('throws error when sendMessage is called without config', async () => {
59
+ const sqs = createSqsService();
60
+ await expect(sqs.sendMessage('hello')).rejects.toThrow('sqs service is not configured');
61
+ });
62
+
63
+ });
@@ -0,0 +1,27 @@
1
+ import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
2
+ import type { SqsConfig, SendMessageResult } from "./types.js";
3
+
4
+ export function createSqsService(config?: SqsConfig) {
5
+ const ensureConfig = () => {
6
+ if (!config) throw new Error("sqs service is not configured -- call .sqs(config) on the suite");
7
+ };
8
+ const client = config ? new SQSClient({ region: config.region }) : null;
9
+
10
+ return {
11
+ sendMessage: async (body: string, attributes?: Record<string, string>): Promise<SendMessageResult> => {
12
+ ensureConfig();
13
+ const response = await client!.send(
14
+ new SendMessageCommand({
15
+ QueueUrl: config!.queueUrl,
16
+ MessageBody: body,
17
+ ...(attributes && {
18
+ MessageAttributes: Object.fromEntries(
19
+ Object.entries(attributes).map(([k, v]) => [k, { DataType: "String", StringValue: v }])
20
+ ),
21
+ }),
22
+ })
23
+ );
24
+ return { messageId: response.MessageId ?? null };
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,8 @@
1
+ export interface SqsConfig {
2
+ queueUrl: string;
3
+ region: string;
4
+ }
5
+
6
+ export interface SendMessageResult {
7
+ messageId: string | null;
8
+ }
@@ -0,0 +1 @@
1
+ OPENAI_API_KEY=
@@ -0,0 +1,27 @@
1
+ import "../load-env.js";
2
+ import { suite } from "../suite.js";
3
+
4
+ await suite()
5
+ .openAi({
6
+ baseUrl: "https://api.openai.com",
7
+ })
8
+ .step("list-models", async (ctx) => {
9
+ const token = `Bearer ${process.env.OPENAI_API_KEY!}`;
10
+ const result = await ctx.services.openAi.listModels(token);
11
+ console.log(`Available models (${result.data.length}):`);
12
+ result.data.slice(0, 5).forEach((m) => console.log(" -", m.id));
13
+ return result;
14
+ })
15
+ .step("send-response", async (ctx) => {
16
+ const token = `Bearer ${process.env.OPENAI_API_KEY!}`;
17
+ const result = await ctx.services.openAi.response(
18
+ token,
19
+ "gpt-4o-mini",
20
+ "text",
21
+ "Say hello in one sentence."
22
+ );
23
+ const text = result.output[0]?.content[0]?.text;
24
+ console.log("response:", text);
25
+ return result;
26
+ })
27
+ .run("throwOnError");
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "open-ai",
3
+ "description": "OpenAI -- list models and send responses",
4
+ "files": [
5
+ { "src": "service.ts", "dest": "src/services/open-ai.ts" },
6
+ { "src": "types.ts", "dest": "src/types/open-ai.ts" },
7
+ { "src": "service.test.ts", "dest": "src/services/open-ai.test.ts" }
8
+ ],
9
+ "env": "env.txt",
10
+ "example": "example.ts",
11
+ "transforms": {
12
+ "service.ts": [
13
+ { "from": "./types.js", "to": "../types/open-ai.js" },
14
+ { "from": "../../init/src/lib/http.js", "to": "../lib/http.js" }
15
+ ],
16
+ "service.test.ts": [
17
+ { "from": "./service.js", "to": "./open-ai.js" },
18
+ { "from": "../../init/src/lib/http.js", "to": "../lib/http.js" }
19
+ ]
20
+ },
21
+ "patches": {
22
+ "src/suite.ts": {
23
+ "imports": [
24
+ "import { createOpenAIService } from \"./services/open-ai.js\";",
25
+ "import type { OpenAiConfig } from \"./types/open-ai.js\";"
26
+ ],
27
+ "configs": " private openAiConfig?: OpenAiConfig;",
28
+ "methods": " openAi(config: OpenAiConfig): this { this.openAiConfig = config; return this; }",
29
+ "services": " openAi: createOpenAIService(this.openAiConfig),"
30
+ },
31
+ "src/types/suite.ts": {
32
+ "type-imports": "import type { OpenAiConfig, OpenAiListModels, OpenAiResponse } from \"./open-ai.js\";",
33
+ "service-types": " openAi: { listModels(bearerToken: string): Promise<OpenAiListModels>; response(bearerToken: string, model: string, format: string, message: string): Promise<OpenAiResponse>; };"
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,55 @@
1
+ import { createOpenAIService } from './service.js';
2
+
3
+ jest.mock('../../init/src/lib/http.js');
4
+ import { httpGet, httpPost } from '../../init/src/lib/http.js';
5
+
6
+ const mockHttpGet = jest.mocked(httpGet);
7
+ const mockHttpPost = jest.mocked(httpPost);
8
+
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ describe('createOpenAIService', () => {
14
+ const config = { baseUrl: 'https://api.openai.com' };
15
+ const bearerToken = 'Bearer sk-test-token';
16
+
17
+ describe('listModels', () => {
18
+ it('calls the correct models endpoint with the bearer token', async () => {
19
+ mockHttpGet.mockResolvedValue({ object: 'list', data: [] });
20
+ const openAi = createOpenAIService(config);
21
+ await openAi.listModels(bearerToken);
22
+ expect(mockHttpGet).toHaveBeenCalledWith(
23
+ 'https://api.openai.com/v1/models',
24
+ { headers: { Authorization: bearerToken } }
25
+ );
26
+ });
27
+
28
+ });
29
+
30
+ describe('response', () => {
31
+ it('calls the correct responses endpoint', async () => {
32
+ mockHttpPost.mockResolvedValue({ id: 'resp-1' });
33
+ const openAi = createOpenAIService(config);
34
+ await openAi.response(bearerToken, 'gpt-4o-mini', 'text', 'Say hello.');
35
+ expect(mockHttpPost).toHaveBeenCalledWith(
36
+ 'https://api.openai.com/v1/responses',
37
+ expect.any(Object),
38
+ { headers: { Authorization: bearerToken } }
39
+ );
40
+ });
41
+
42
+ it('sends the correct request body', async () => {
43
+ mockHttpPost.mockResolvedValue({ id: 'resp-1' });
44
+ const openAi = createOpenAIService(config);
45
+ await openAi.response(bearerToken, 'gpt-4o-mini', 'json_object', 'Return JSON.');
46
+ const body = mockHttpPost.mock.calls[0][1];
47
+ expect(body).toMatchObject({
48
+ model: 'gpt-4o-mini',
49
+ input: 'Return JSON.',
50
+ text: { format: { type: 'json_object' } },
51
+ });
52
+ });
53
+
54
+ });
55
+ });
@@ -0,0 +1,26 @@
1
+ import type { OpenAiConfig, OpenAiListModels, OpenAiResponse, OpenAiResponseBody, OpenAiResponseFormat } from "./types.js";
2
+ import { httpGet, httpPost } from "../../init/src/lib/http.js";
3
+
4
+ export function createOpenAIService(config?: OpenAiConfig) {
5
+ const ensureConfig = () => {
6
+ if (!config) throw new Error("openAi service is not configured -- call .openAi(config) on the suite");
7
+ };
8
+
9
+ return {
10
+ listModels: async (bearerToken: string): Promise<OpenAiListModels> => {
11
+ ensureConfig();
12
+ const headers: Record<string, string> = { Authorization: bearerToken };
13
+ return httpGet<OpenAiListModels>(`${config!.baseUrl}/v1/models`, { headers });
14
+ },
15
+ response: async (bearerToken: string, model: string, format: OpenAiResponseFormat, message: string): Promise<OpenAiResponse> => {
16
+ ensureConfig();
17
+ const headers: Record<string, string> = { Authorization: bearerToken };
18
+ const body: OpenAiResponseBody = {
19
+ model,
20
+ input: message,
21
+ text: { format: { type: format } },
22
+ };
23
+ return httpPost<OpenAiResponseBody, OpenAiResponse>(`${config!.baseUrl}/v1/responses`, body, { headers });
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,61 @@
1
+ export interface OpenAiConfig {
2
+ baseUrl: string;
3
+ }
4
+
5
+ export interface OpenAiListModels {
6
+ object: string;
7
+ data: OpenAiModel[];
8
+ }
9
+
10
+ export interface OpenAiModel {
11
+ id: string;
12
+ object: string;
13
+ created: number;
14
+ owned_by: string;
15
+ }
16
+
17
+ export type OpenAiResponseFormat = "text" | "json_object";
18
+
19
+ export interface OpenAiResponseBody {
20
+ model: string;
21
+ input: string;
22
+ text?: {
23
+ format: {
24
+ type: OpenAiResponseFormat;
25
+ };
26
+ };
27
+ }
28
+
29
+ export interface OpenAiResponse {
30
+ id: string;
31
+ object: string;
32
+ created_at: number;
33
+ status: string;
34
+ background: boolean;
35
+ billing: {
36
+ payer: string;
37
+ };
38
+ error: unknown;
39
+ incomplete_details: unknown;
40
+ instructions: unknown;
41
+ max_output_tokens: unknown;
42
+ max_tool_calls: unknown;
43
+ model: string;
44
+ output: [
45
+ {
46
+ id: string;
47
+ type: string;
48
+ status: string;
49
+ content: [
50
+ {
51
+ type: string;
52
+ annotations: unknown;
53
+ logprobs: unknown;
54
+ text: string;
55
+ }
56
+ ];
57
+ role: string;
58
+ }
59
+ ];
60
+ parallel_tool_calls: boolean;
61
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "types": ["jest", "node"]
10
+ }
11
+ }