@shipfox/api-integration-jira 12.0.0 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +17 -2
- package/CHANGELOG.md +10 -0
- package/README.md +58 -2
- package/dist/api/client.d.ts +19 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +78 -0
- package/dist/api/client.js.map +1 -1
- package/dist/core/agent-tools-provider.d.ts +26 -0
- package/dist/core/agent-tools-provider.d.ts.map +1 -0
- package/dist/core/agent-tools-provider.js +130 -0
- package/dist/core/agent-tools-provider.js.map +1 -0
- package/dist/core/agent-tools.d.ts +16 -0
- package/dist/core/agent-tools.d.ts.map +1 -0
- package/dist/core/agent-tools.js +441 -0
- package/dist/core/agent-tools.js.map +1 -0
- package/dist/core/tokens.d.ts +2 -0
- package/dist/core/tokens.d.ts.map +1 -1
- package/dist/core/tokens.js +129 -37
- package/dist/core/tokens.js.map +1 -1
- package/dist/db/db.d.ts +68 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/installations.d.ts +23 -7
- package/dist/db/installations.d.ts.map +1 -1
- package/dist/db/installations.js +42 -19
- package/dist/db/installations.js.map +1 -1
- package/dist/db/schema/installations.d.ts +34 -0
- package/dist/db/schema/installations.d.ts.map +1 -1
- package/dist/db/schema/installations.js +12 -2
- package/dist/db/schema/installations.js.map +1 -1
- package/dist/index.d.ts +20 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -5
- package/dist/index.js.map +1 -1
- package/dist/temporal/activities/index.d.ts +2 -0
- package/dist/temporal/activities/index.d.ts.map +1 -0
- package/dist/temporal/activities/index.js +3 -0
- package/dist/temporal/activities/index.js.map +1 -0
- package/dist/temporal/activities/refresh-jira-tokens.d.ts +20 -0
- package/dist/temporal/activities/refresh-jira-tokens.d.ts.map +1 -0
- package/dist/temporal/activities/refresh-jira-tokens.js +58 -0
- package/dist/temporal/activities/refresh-jira-tokens.js.map +1 -0
- package/dist/temporal/constants.d.ts +6 -0
- package/dist/temporal/constants.d.ts.map +1 -0
- package/dist/temporal/constants.js +8 -0
- package/dist/temporal/constants.js.map +1 -0
- package/dist/temporal/worker.d.ts +9 -0
- package/dist/temporal/worker.d.ts.map +1 -0
- package/dist/temporal/worker.js +26 -0
- package/dist/temporal/worker.js.map +1 -0
- package/dist/temporal/workflows/index.bundle.js +25225 -0
- package/dist/temporal/workflows/index.bundle.meta.json +1 -0
- package/dist/temporal/workflows/index.d.ts +2 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -0
- package/dist/temporal/workflows/index.js +3 -0
- package/dist/temporal/workflows/index.js.map +1 -0
- package/dist/temporal/workflows/refresh-jira-tokens-cron.d.ts +2 -0
- package/dist/temporal/workflows/refresh-jira-tokens-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/refresh-jira-tokens-cron.js +20 -0
- package/dist/temporal/workflows/refresh-jira-tokens-cron.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0001_jira-token-refresh-state.sql +12 -0
- package/drizzle/meta/0001_snapshot.json +245 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +12 -5
- package/src/api/client.test.ts +133 -12
- package/src/api/client.ts +107 -0
- package/src/core/agent-tools-provider.test.ts +299 -0
- package/src/core/agent-tools-provider.ts +175 -0
- package/src/core/agent-tools.ts +450 -0
- package/src/core/tokens-refresh.test.ts +221 -4
- package/src/core/tokens.test.ts +1 -1
- package/src/core/tokens.ts +153 -33
- package/src/db/installations.test.ts +84 -2
- package/src/db/installations.ts +81 -21
- package/src/db/schema/installations.ts +21 -1
- package/src/index.test.ts +34 -0
- package/src/index.ts +47 -3
- package/src/temporal/activities/index.ts +5 -0
- package/src/temporal/activities/refresh-jira-tokens.test.ts +156 -0
- package/src/temporal/activities/refresh-jira-tokens.ts +88 -0
- package/src/temporal/constants.ts +8 -0
- package/src/temporal/worker.ts +39 -0
- package/src/temporal/workflows/index.ts +1 -0
- package/src/temporal/workflows/refresh-jira-tokens-cron.ts +19 -0
- package/test/factories/jira-installation.ts +4 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
1
|
+
$ shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js
|
|
2
|
+
Successfully compiled: 32 files with swc (419.4ms)
|
|
3
|
+
2026-08-04T14:51:23.448Z [INFO] asset workflow-bundle-21cc907e680c539b74ef.js 3.17 MiB [emitted] [immutable] (name: main)
|
|
4
|
+
orphan modules 33.5 KiB [orphan] 21 modules
|
|
5
|
+
runtime modules 1.13 KiB 5 modules
|
|
6
|
+
modules by path ../../../../node_modules/.pnpm/ 970 KiB 196 modules
|
|
7
|
+
modules by path ./dist/temporal/workflows/ 1.64 KiB
|
|
8
|
+
./dist/temporal/workflows/index-autogenerated-entrypoint.cjs 941 bytes [built] [code generated]
|
|
9
|
+
./dist/temporal/workflows/index.js 72 bytes [built] [code generated]
|
|
10
|
+
./dist/temporal/workflows/refresh-jira-tokens-cron.js 666 bytes [built] [code generated]
|
|
11
|
+
optional modules 30 bytes [optional]
|
|
12
|
+
__temporal_custom_payload_converter (ignored) 15 bytes [optional] [built] [code generated]
|
|
13
|
+
__temporal_custom_failure_converter (ignored) 15 bytes [optional] [built] [code generated]
|
|
14
|
+
../../../shared/node/temporal/dist/workflow-error-interceptor.js 1.21 KiB [built] [code generated]
|
|
15
|
+
webpack 5.107.2 compiled successfully in 7200 ms
|
|
16
|
+
2026-08-04T14:51:23.467Z [INFO] Workflow bundle created { size: '3.17MB' }
|
|
17
|
+
/home/runner/work/shipfox/shipfox/libs/api/integration/jira/dist/temporal/workflows/index.js: 3323887 bytes
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @shipfox/api-integration-jira
|
|
2
2
|
|
|
3
|
+
## 12.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 70e33c0: Adds the in-process Jira REST agent-tool catalog and write-selection support.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- af7adfc: Adds a six-hour Jira refresh-token maintenance worker that refreshes idle tokens and migrates existing installations with backfilled refresh state. Token access now fails closed for non-active connections, and rejected refresh tokens or ambiguous refresh timeouts require reconnecting Jira.
|
|
12
|
+
|
|
3
13
|
## 12.0.0
|
|
4
14
|
|
|
5
15
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Jira integration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Jira OAuth, site links, token storage, webhooks, and token upkeep for Shipfox.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **`createJiraIntegrationProvider`** builds Jira OAuth and webhook routes.
|
|
8
|
+
- **`createJiraTokenStore`** stores Jira access and refresh tokens.
|
|
9
|
+
- **`createJiraMaintenanceWorker`** runs the token refresh workflow.
|
|
10
|
+
- **Installation functions** store Jira sites, webhooks, and token dates.
|
|
11
|
+
|
|
12
|
+
## Token upkeep
|
|
13
|
+
|
|
14
|
+
The worker runs every six hours.
|
|
15
|
+
|
|
16
|
+
It checks active sites in small batches. It refreshes a token after 76 idle days. Jira refresh tokens expire after about 90 idle days. The 14-day lead gives time for recovery.
|
|
17
|
+
|
|
18
|
+
A rejected refresh token or timeout marks the link as `error`. The user must connect Jira again. A new OAuth flow clears this state.
|
|
19
|
+
|
|
20
|
+
## Installation and setup
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pnpm add @shipfox/api-integration-jira
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The host supplies OAuth config, secret storage, connection lookup, and database access.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {createJiraIntegrationProvider} from '@shipfox/api-integration-jira';
|
|
32
|
+
|
|
33
|
+
const provider = createJiraIntegrationProvider();
|
|
34
|
+
|
|
35
|
+
console.log(provider.provider); // "jira"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The composed integrations module supplies route and worker dependencies.
|
|
39
|
+
|
|
40
|
+
## Behavior notes
|
|
41
|
+
|
|
42
|
+
Connect Jira with a dedicated Shipfox service account. Jira 3LO actions use the authorizing account. Events from that account are dropped to prevent agent self-triggering.
|
|
43
|
+
|
|
44
|
+
## Data model
|
|
45
|
+
|
|
46
|
+
The package owns the `integrations_jira_installations` table. The `refresh_token_last_used_at` column records the last successful refresh-token use. The `refresh_token_last_attempted_at` column rotates maintenance attempts so failed or inactive connections cannot starve the capped sweep.
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
mise exec -- turbo check --filter=@shipfox/api-integration-jira
|
|
52
|
+
mise exec -- turbo type --filter=@shipfox/api-integration-jira
|
|
53
|
+
mise exec -- turbo test --filter=@shipfox/api-integration-jira
|
|
54
|
+
mise exec -- turbo build --filter=@shipfox/api-integration-jira
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Database tests need the local PostgreSQL test service.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
package/dist/api/client.d.ts
CHANGED
|
@@ -18,6 +18,24 @@ export interface JiraIdentity {
|
|
|
18
18
|
export interface JiraDynamicWebhookRegistration {
|
|
19
19
|
webhookId: number;
|
|
20
20
|
}
|
|
21
|
+
export type JiraAgentToolHttpMethod = 'GET' | 'POST' | 'PUT';
|
|
22
|
+
export type JiraAgentToolQueryValue = string | number | boolean | readonly (string | number)[] | undefined;
|
|
23
|
+
export interface JiraAgentToolRequest {
|
|
24
|
+
accessToken: string;
|
|
25
|
+
cloudId: string;
|
|
26
|
+
method: JiraAgentToolHttpMethod;
|
|
27
|
+
path: string;
|
|
28
|
+
query?: Record<string, JiraAgentToolQueryValue> | undefined;
|
|
29
|
+
body?: unknown;
|
|
30
|
+
operation?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
export interface JiraAgentToolResponse {
|
|
33
|
+
status: number;
|
|
34
|
+
body: unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface JiraAgentToolsClient {
|
|
37
|
+
request(input: JiraAgentToolRequest): Promise<JiraAgentToolResponse>;
|
|
38
|
+
}
|
|
21
39
|
export interface JiraApiClient {
|
|
22
40
|
exchangeAuthorizationCode(input: {
|
|
23
41
|
code: string;
|
|
@@ -44,5 +62,6 @@ export interface JiraApiClient {
|
|
|
44
62
|
}): Promise<void>;
|
|
45
63
|
}
|
|
46
64
|
export declare function createJiraApiClient(): JiraApiClient;
|
|
65
|
+
export declare function createJiraAgentToolsClient(): JiraAgentToolsClient;
|
|
47
66
|
export declare function mapJiraError<T>(operation: string, request: () => Promise<T>): Promise<T>;
|
|
48
67
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,2BAA2B,sIAAwB,CAAC;AACjE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7D,MAAM,MAAM,uBAAuB,GAC/B,MAAM,GACN,MAAM,GACN,OAAO,GACP,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAC5B,SAAS,CAAC;AAEd,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,uBAAuB,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,GAAG,SAAS,CAAC;IAC5D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,aAAa;IAC5B,yBAAyB,CAAC,KAAK,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC7E,kBAAkB,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9E,sBAAsB,CAAC,KAAK,EAAE;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IACxF,SAAS,CAAC,KAAK,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAChF,sBAAsB,CAAC,KAAK,EAAE;QAC5B,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC5C,oBAAoB,CAAC,KAAK,EAAE;QAC1B,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AA0BD,wBAAgB,mBAAmB,IAAI,aAAa,CAkGnD;AAED,wBAAgB,0BAA0B,IAAI,oBAAoB,CAEjE;AA6ID,wBAAsB,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAyC9F"}
|
package/dist/api/client.js
CHANGED
|
@@ -5,8 +5,13 @@ import { config } from '#config.js';
|
|
|
5
5
|
import { JiraIntegrationProviderError } from '#core/errors.js';
|
|
6
6
|
const JIRA_API_TIMEOUT_MS = 10_000;
|
|
7
7
|
const SCOPE_SEPARATOR_RE = /[,\s]+/;
|
|
8
|
+
const TRAILING_SLASHES_RE = /\/+$/;
|
|
8
9
|
export const JIRA_DYNAMIC_WEBHOOK_EVENTS = jiraWebhookEventNames;
|
|
9
10
|
export const JIRA_DYNAMIC_WEBHOOK_JQL = '';
|
|
11
|
+
const JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES = new Set([
|
|
12
|
+
'invalid_grant',
|
|
13
|
+
'unauthorized_client'
|
|
14
|
+
]);
|
|
10
15
|
export function createJiraApiClient() {
|
|
11
16
|
return {
|
|
12
17
|
async exchangeAuthorizationCode (input) {
|
|
@@ -93,6 +98,68 @@ export function createJiraApiClient() {
|
|
|
93
98
|
}
|
|
94
99
|
};
|
|
95
100
|
}
|
|
101
|
+
export function createJiraAgentToolsClient() {
|
|
102
|
+
return {
|
|
103
|
+
request: requestJiraRest
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function requestJiraRest(input) {
|
|
107
|
+
return await mapJiraError(input.operation ?? 'agent-tool', async ()=>{
|
|
108
|
+
try {
|
|
109
|
+
const response = await ky(jiraRestUrl(input.cloudId, input.path), {
|
|
110
|
+
method: input.method,
|
|
111
|
+
headers: {
|
|
112
|
+
authorization: `Bearer ${input.accessToken}`
|
|
113
|
+
},
|
|
114
|
+
...input.query === undefined ? {} : {
|
|
115
|
+
searchParams: jiraQueryParams(input.query)
|
|
116
|
+
},
|
|
117
|
+
...input.body === undefined ? {} : {
|
|
118
|
+
json: input.body
|
|
119
|
+
},
|
|
120
|
+
timeout: JIRA_API_TIMEOUT_MS
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
status: response.status,
|
|
124
|
+
body: await readJiraResponseBody(response)
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof HTTPError && (error.response.status === 400 || error.response.status === 404)) {
|
|
128
|
+
return {
|
|
129
|
+
status: error.response.status,
|
|
130
|
+
body: await readJiraResponseBody(error.response)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function jiraRestUrl(cloudId, path) {
|
|
138
|
+
const baseUrl = config.JIRA_API_BASE_URL.replace(TRAILING_SLASHES_RE, '');
|
|
139
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
140
|
+
return `${baseUrl}/ex/jira/${encodeURIComponent(cloudId)}/rest/api/3${normalizedPath}`;
|
|
141
|
+
}
|
|
142
|
+
function jiraQueryParams(query) {
|
|
143
|
+
const params = new URLSearchParams();
|
|
144
|
+
for (const [key, value] of Object.entries(query)){
|
|
145
|
+
if (value === undefined) continue;
|
|
146
|
+
if (Array.isArray(value)) {
|
|
147
|
+
for (const item of value)params.append(key, String(item));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
params.set(key, String(value));
|
|
151
|
+
}
|
|
152
|
+
return params.size > 0 ? params : undefined;
|
|
153
|
+
}
|
|
154
|
+
async function readJiraResponseBody(response) {
|
|
155
|
+
const text = await response.text();
|
|
156
|
+
if (text.length === 0) return undefined;
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(text);
|
|
159
|
+
} catch {
|
|
160
|
+
return text;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
96
163
|
function parseDynamicWebhookRegistration(body) {
|
|
97
164
|
if (!body || typeof body !== 'object' || !Array.isArray(body.webhookRegistrationResult) || body.webhookRegistrationResult.length !== 1) {
|
|
98
165
|
throw malformed('Jira webhook registration response did not contain exactly one result');
|
|
@@ -168,6 +235,12 @@ export async function mapJiraError(operation, request) {
|
|
|
168
235
|
if (status === 401 || status === 403) {
|
|
169
236
|
throw new JiraIntegrationProviderError('access-denied', 'Jira request was rejected');
|
|
170
237
|
}
|
|
238
|
+
if (status === 400 && operation === 'refresh-access-token') {
|
|
239
|
+
const errorCode = readJiraOAuthErrorCode(error.data);
|
|
240
|
+
if (errorCode && JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES.has(errorCode)) {
|
|
241
|
+
throw new JiraIntegrationProviderError('access-denied', 'Jira refresh token was rejected; reconnect is required');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
171
244
|
throw malformed('Jira request was rejected');
|
|
172
245
|
}
|
|
173
246
|
if (error instanceof TimeoutError) {
|
|
@@ -186,6 +259,11 @@ export async function mapJiraError(operation, request) {
|
|
|
186
259
|
function malformed(message) {
|
|
187
260
|
return new JiraIntegrationProviderError('malformed-provider-response', message);
|
|
188
261
|
}
|
|
262
|
+
function readJiraOAuthErrorCode(data) {
|
|
263
|
+
if (!data || typeof data !== 'object') return undefined;
|
|
264
|
+
const errorCode = data.error;
|
|
265
|
+
return typeof errorCode === 'string' ? errorCode : undefined;
|
|
266
|
+
}
|
|
189
267
|
function retryAfterSeconds(headers) {
|
|
190
268
|
const value = headers.get('retry-after');
|
|
191
269
|
if (!value) return undefined;
|
package/dist/api/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {jiraWebhookEventNames} from '@shipfox/api-integration-jira-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport ky, {HTTPError, TimeoutError} from 'ky';\nimport {config} from '#config.js';\nimport {JiraIntegrationProviderError} from '#core/errors.js';\n\nconst JIRA_API_TIMEOUT_MS = 10_000;\nconst SCOPE_SEPARATOR_RE = /[,\\s]+/;\nexport const JIRA_DYNAMIC_WEBHOOK_EVENTS = jiraWebhookEventNames;\nexport const JIRA_DYNAMIC_WEBHOOK_JQL = '';\n\nexport interface JiraAuthorization {\n accessToken: string;\n refreshToken?: string | undefined;\n expiresAt?: Date | undefined;\n scopes: string[];\n}\n\nexport interface JiraAccessibleResource {\n cloudId: string;\n name: string;\n url: string;\n scopes: string[];\n}\n\nexport interface JiraIdentity {\n accountId: string;\n}\n\nexport interface JiraDynamicWebhookRegistration {\n webhookId: number;\n}\n\nexport interface JiraApiClient {\n exchangeAuthorizationCode(input: {code: string}): Promise<JiraAuthorization>;\n refreshAccessToken(input: {refreshToken: string}): Promise<JiraAuthorization>;\n getAccessibleResources(input: {accessToken: string}): Promise<JiraAccessibleResource[]>;\n getMyself(input: {accessToken: string; cloudId: string}): Promise<JiraIdentity>;\n registerDynamicWebhook(input: {\n accessToken: string;\n cloudId: string;\n url: string;\n }): Promise<JiraDynamicWebhookRegistration>;\n deleteDynamicWebhook(input: {\n accessToken: string;\n cloudId: string;\n webhookId: number;\n }): Promise<void>;\n}\n\ninterface JiraTokenResponse {\n access_token?: unknown;\n refresh_token?: unknown;\n expires_in?: unknown;\n scope?: unknown;\n}\n\ninterface JiraResourceResponse {\n id?: unknown;\n name?: unknown;\n url?: unknown;\n scopes?: unknown;\n}\n\ninterface JiraMyselfResponse {\n accountId?: unknown;\n}\n\ninterface JiraDynamicWebhookRegistrationResponse {\n webhookRegistrationResult?: unknown;\n}\n\nexport function createJiraApiClient(): JiraApiClient {\n return {\n async exchangeAuthorizationCode(input) {\n const body = await mapJiraError('exchange-authorization-code', () =>\n ky\n .post(`${config.JIRA_AUTH_BASE_URL}/oauth/token`, {\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: config.JIRA_OAUTH_CLIENT_ID,\n client_secret: config.JIRA_OAUTH_CLIENT_SECRET,\n code: input.code,\n redirect_uri: config.JIRA_OAUTH_REDIRECT_URL,\n }),\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraTokenResponse>(),\n );\n return parseAuthorization(body);\n },\n\n async refreshAccessToken(input) {\n const body = await mapJiraError('refresh-access-token', () =>\n ky\n .post(`${config.JIRA_AUTH_BASE_URL}/oauth/token`, {\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: config.JIRA_OAUTH_CLIENT_ID,\n client_secret: config.JIRA_OAUTH_CLIENT_SECRET,\n refresh_token: input.refreshToken,\n }),\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraTokenResponse>(),\n );\n return parseAuthorization(body);\n },\n\n async getAccessibleResources(input) {\n const body = await mapJiraError('get-accessible-resources', () =>\n ky\n .get(`${config.JIRA_API_BASE_URL}/oauth/token/accessible-resources`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<unknown>(),\n );\n if (!Array.isArray(body)) {\n throw malformed('Jira accessible-resources response was not an array');\n }\n return body.map(parseAccessibleResource);\n },\n\n async getMyself(input) {\n const body = await mapJiraError('get-myself', () =>\n ky\n .get(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/myself`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraMyselfResponse>(),\n );\n if (typeof body.accountId !== 'string' || body.accountId.length === 0) {\n throw malformed('Jira identity response did not include an accountId');\n }\n return {accountId: body.accountId};\n },\n\n async registerDynamicWebhook(input) {\n const body = await mapJiraError('register-dynamic-webhook', () =>\n ky\n .post(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/webhook`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n json: {\n url: input.url,\n webhooks: [\n {\n events: JIRA_DYNAMIC_WEBHOOK_EVENTS,\n jqlFilter: JIRA_DYNAMIC_WEBHOOK_JQL,\n },\n ],\n },\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraDynamicWebhookRegistrationResponse>(),\n );\n return parseDynamicWebhookRegistration(body);\n },\n\n async deleteDynamicWebhook(input) {\n await mapJiraError('delete-dynamic-webhook', () =>\n ky.delete(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/webhook`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n json: {webhookIds: [input.webhookId]},\n timeout: JIRA_API_TIMEOUT_MS,\n }),\n );\n },\n };\n}\n\nfunction parseDynamicWebhookRegistration(\n body: JiraDynamicWebhookRegistrationResponse,\n): JiraDynamicWebhookRegistration {\n if (\n !body ||\n typeof body !== 'object' ||\n !Array.isArray(body.webhookRegistrationResult) ||\n body.webhookRegistrationResult.length !== 1\n ) {\n throw malformed('Jira webhook registration response did not contain exactly one result');\n }\n\n const result = body.webhookRegistrationResult[0];\n if (!result || typeof result !== 'object') {\n throw malformed('Jira webhook registration result was malformed');\n }\n const {createdWebhookId, errors} = result as {\n createdWebhookId?: unknown;\n errors?: unknown;\n };\n if (errors !== undefined && (!Array.isArray(errors) || errors.length > 0)) {\n throw malformed('Jira webhook registration returned errors');\n }\n if (\n typeof createdWebhookId !== 'number' ||\n !Number.isSafeInteger(createdWebhookId) ||\n createdWebhookId <= 0\n ) {\n throw malformed('Jira webhook registration did not return a created webhook id');\n }\n return {webhookId: createdWebhookId};\n}\n\nfunction parseAuthorization(body: JiraTokenResponse): JiraAuthorization {\n if (typeof body.access_token !== 'string' || body.access_token.length === 0) {\n throw malformed('Jira authorization response did not include an access token');\n }\n return {\n accessToken: body.access_token,\n refreshToken: typeof body.refresh_token === 'string' ? body.refresh_token : undefined,\n expiresAt: parseExpiresAt(body.expires_in),\n scopes: parseScopes(body.scope),\n };\n}\n\nfunction parseAccessibleResource(value: unknown): JiraAccessibleResource {\n if (!value || typeof value !== 'object') throw malformed('Jira site response was malformed');\n const {id, name, url, scopes} = value as JiraResourceResponse;\n if (\n typeof id !== 'string' ||\n id.length === 0 ||\n typeof name !== 'string' ||\n name.length === 0 ||\n typeof url !== 'string' ||\n url.length === 0 ||\n !Array.isArray(scopes) ||\n !scopes.every((scope) => typeof scope === 'string')\n ) {\n throw malformed('Jira site response did not include a valid cloud id, name, URL, and scopes');\n }\n return {cloudId: id, name, url, scopes};\n}\n\nfunction parseScopes(scope: unknown): string[] {\n if (typeof scope === 'string')\n return scope\n .split(SCOPE_SEPARATOR_RE)\n .map((value) => value.trim())\n .filter(Boolean);\n if (Array.isArray(scope) && scope.every((value) => typeof value === 'string')) return scope;\n if (scope === undefined) return [];\n throw malformed('Jira authorization response included malformed scopes');\n}\n\nfunction parseExpiresAt(expiresIn: unknown): Date | undefined {\n if (expiresIn === undefined) return undefined;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw malformed('Jira authorization response included a malformed expiry');\n }\n return new Date(Date.now() + expiresIn * 1000);\n}\n\nexport async function mapJiraError<T>(operation: string, request: () => Promise<T>): Promise<T> {\n try {\n return await request();\n } catch (error) {\n if (error instanceof JiraIntegrationProviderError) throw error;\n if (error instanceof HTTPError) {\n const {status, statusText, headers} = error.response;\n logger().warn({operation, status, statusText}, 'Jira API request rejected');\n if (status === 429) {\n throw new JiraIntegrationProviderError(\n 'rate-limited',\n 'Jira request was rate limited',\n retryAfterSeconds(headers),\n );\n }\n if (status >= 500)\n throw new JiraIntegrationProviderError('provider-unavailable', 'Jira request failed');\n if (status === 401 || status === 403) {\n throw new JiraIntegrationProviderError('access-denied', 'Jira request was rejected');\n }\n throw malformed('Jira request was rejected');\n }\n if (error instanceof TimeoutError) {\n logger().warn({operation}, 'Jira API request timed out');\n throw new JiraIntegrationProviderError('timeout', 'Jira request timed out');\n }\n logger().warn(\n {operation, errName: error instanceof Error ? error.name : typeof error},\n 'Jira API request failed',\n );\n throw new JiraIntegrationProviderError('provider-unavailable', 'Jira request failed');\n }\n}\n\nfunction malformed(message: string): JiraIntegrationProviderError {\n return new JiraIntegrationProviderError('malformed-provider-response', message);\n}\n\nfunction retryAfterSeconds(headers: Headers): number | undefined {\n const value = headers.get('retry-after');\n if (!value) return undefined;\n const parsed = Number.parseInt(value, 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n"],"names":["jiraWebhookEventNames","logger","ky","HTTPError","TimeoutError","config","JiraIntegrationProviderError","JIRA_API_TIMEOUT_MS","SCOPE_SEPARATOR_RE","JIRA_DYNAMIC_WEBHOOK_EVENTS","JIRA_DYNAMIC_WEBHOOK_JQL","createJiraApiClient","exchangeAuthorizationCode","input","body","mapJiraError","post","JIRA_AUTH_BASE_URL","URLSearchParams","grant_type","client_id","JIRA_OAUTH_CLIENT_ID","client_secret","JIRA_OAUTH_CLIENT_SECRET","code","redirect_uri","JIRA_OAUTH_REDIRECT_URL","timeout","json","parseAuthorization","refreshAccessToken","refresh_token","refreshToken","getAccessibleResources","get","JIRA_API_BASE_URL","headers","authorization","accessToken","Array","isArray","malformed","map","parseAccessibleResource","getMyself","cloudId","accountId","length","registerDynamicWebhook","url","webhooks","events","jqlFilter","parseDynamicWebhookRegistration","deleteDynamicWebhook","delete","webhookIds","webhookId","webhookRegistrationResult","result","createdWebhookId","errors","undefined","Number","isSafeInteger","access_token","expiresAt","parseExpiresAt","expires_in","scopes","parseScopes","scope","value","id","name","every","split","trim","filter","Boolean","expiresIn","isFinite","Date","now","operation","request","error","status","statusText","response","warn","retryAfterSeconds","errName","Error","message","parsed","parseInt","isNaN"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {jiraWebhookEventNames} from '@shipfox/api-integration-jira-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport ky, {HTTPError, TimeoutError} from 'ky';\nimport {config} from '#config.js';\nimport {JiraIntegrationProviderError} from '#core/errors.js';\n\nconst JIRA_API_TIMEOUT_MS = 10_000;\nconst SCOPE_SEPARATOR_RE = /[,\\s]+/;\nconst TRAILING_SLASHES_RE = /\\/+$/;\nexport const JIRA_DYNAMIC_WEBHOOK_EVENTS = jiraWebhookEventNames;\nexport const JIRA_DYNAMIC_WEBHOOK_JQL = '';\n\nexport interface JiraAuthorization {\n accessToken: string;\n refreshToken?: string | undefined;\n expiresAt?: Date | undefined;\n scopes: string[];\n}\n\nexport interface JiraAccessibleResource {\n cloudId: string;\n name: string;\n url: string;\n scopes: string[];\n}\n\nexport interface JiraIdentity {\n accountId: string;\n}\n\nexport interface JiraDynamicWebhookRegistration {\n webhookId: number;\n}\n\nexport type JiraAgentToolHttpMethod = 'GET' | 'POST' | 'PUT';\n\nexport type JiraAgentToolQueryValue =\n | string\n | number\n | boolean\n | readonly (string | number)[]\n | undefined;\n\nexport interface JiraAgentToolRequest {\n accessToken: string;\n cloudId: string;\n method: JiraAgentToolHttpMethod;\n path: string;\n query?: Record<string, JiraAgentToolQueryValue> | undefined;\n body?: unknown;\n operation?: string | undefined;\n}\n\nexport interface JiraAgentToolResponse {\n status: number;\n body: unknown;\n}\n\nexport interface JiraAgentToolsClient {\n request(input: JiraAgentToolRequest): Promise<JiraAgentToolResponse>;\n}\n\nexport interface JiraApiClient {\n exchangeAuthorizationCode(input: {code: string}): Promise<JiraAuthorization>;\n refreshAccessToken(input: {refreshToken: string}): Promise<JiraAuthorization>;\n getAccessibleResources(input: {accessToken: string}): Promise<JiraAccessibleResource[]>;\n getMyself(input: {accessToken: string; cloudId: string}): Promise<JiraIdentity>;\n registerDynamicWebhook(input: {\n accessToken: string;\n cloudId: string;\n url: string;\n }): Promise<JiraDynamicWebhookRegistration>;\n deleteDynamicWebhook(input: {\n accessToken: string;\n cloudId: string;\n webhookId: number;\n }): Promise<void>;\n}\n\ninterface JiraTokenResponse {\n access_token?: unknown;\n refresh_token?: unknown;\n expires_in?: unknown;\n scope?: unknown;\n}\n\ninterface JiraResourceResponse {\n id?: unknown;\n name?: unknown;\n url?: unknown;\n scopes?: unknown;\n}\n\ninterface JiraMyselfResponse {\n accountId?: unknown;\n}\n\ninterface JiraDynamicWebhookRegistrationResponse {\n webhookRegistrationResult?: unknown;\n}\n\nconst JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES = new Set(['invalid_grant', 'unauthorized_client']);\n\nexport function createJiraApiClient(): JiraApiClient {\n return {\n async exchangeAuthorizationCode(input) {\n const body = await mapJiraError('exchange-authorization-code', () =>\n ky\n .post(`${config.JIRA_AUTH_BASE_URL}/oauth/token`, {\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: config.JIRA_OAUTH_CLIENT_ID,\n client_secret: config.JIRA_OAUTH_CLIENT_SECRET,\n code: input.code,\n redirect_uri: config.JIRA_OAUTH_REDIRECT_URL,\n }),\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraTokenResponse>(),\n );\n return parseAuthorization(body);\n },\n\n async refreshAccessToken(input) {\n const body = await mapJiraError('refresh-access-token', () =>\n ky\n .post(`${config.JIRA_AUTH_BASE_URL}/oauth/token`, {\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: config.JIRA_OAUTH_CLIENT_ID,\n client_secret: config.JIRA_OAUTH_CLIENT_SECRET,\n refresh_token: input.refreshToken,\n }),\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraTokenResponse>(),\n );\n return parseAuthorization(body);\n },\n\n async getAccessibleResources(input) {\n const body = await mapJiraError('get-accessible-resources', () =>\n ky\n .get(`${config.JIRA_API_BASE_URL}/oauth/token/accessible-resources`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<unknown>(),\n );\n if (!Array.isArray(body)) {\n throw malformed('Jira accessible-resources response was not an array');\n }\n return body.map(parseAccessibleResource);\n },\n\n async getMyself(input) {\n const body = await mapJiraError('get-myself', () =>\n ky\n .get(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/myself`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraMyselfResponse>(),\n );\n if (typeof body.accountId !== 'string' || body.accountId.length === 0) {\n throw malformed('Jira identity response did not include an accountId');\n }\n return {accountId: body.accountId};\n },\n\n async registerDynamicWebhook(input) {\n const body = await mapJiraError('register-dynamic-webhook', () =>\n ky\n .post(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/webhook`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n json: {\n url: input.url,\n webhooks: [\n {\n events: JIRA_DYNAMIC_WEBHOOK_EVENTS,\n jqlFilter: JIRA_DYNAMIC_WEBHOOK_JQL,\n },\n ],\n },\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraDynamicWebhookRegistrationResponse>(),\n );\n return parseDynamicWebhookRegistration(body);\n },\n\n async deleteDynamicWebhook(input) {\n await mapJiraError('delete-dynamic-webhook', () =>\n ky.delete(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/webhook`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n json: {webhookIds: [input.webhookId]},\n timeout: JIRA_API_TIMEOUT_MS,\n }),\n );\n },\n };\n}\n\nexport function createJiraAgentToolsClient(): JiraAgentToolsClient {\n return {request: requestJiraRest};\n}\n\nasync function requestJiraRest(input: JiraAgentToolRequest): Promise<JiraAgentToolResponse> {\n return await mapJiraError(input.operation ?? 'agent-tool', async () => {\n try {\n const response = await ky(jiraRestUrl(input.cloudId, input.path), {\n method: input.method,\n headers: {authorization: `Bearer ${input.accessToken}`},\n ...(input.query === undefined ? {} : {searchParams: jiraQueryParams(input.query)}),\n ...(input.body === undefined ? {} : {json: input.body}),\n timeout: JIRA_API_TIMEOUT_MS,\n });\n return {status: response.status, body: await readJiraResponseBody(response)};\n } catch (error) {\n if (\n error instanceof HTTPError &&\n (error.response.status === 400 || error.response.status === 404)\n ) {\n return {\n status: error.response.status,\n body: await readJiraResponseBody(error.response),\n };\n }\n throw error;\n }\n });\n}\n\nfunction jiraRestUrl(cloudId: string, path: string): string {\n const baseUrl = config.JIRA_API_BASE_URL.replace(TRAILING_SLASHES_RE, '');\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n return `${baseUrl}/ex/jira/${encodeURIComponent(cloudId)}/rest/api/3${normalizedPath}`;\n}\n\nfunction jiraQueryParams(\n query: Record<string, JiraAgentToolQueryValue>,\n): URLSearchParams | undefined {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) params.append(key, String(item));\n continue;\n }\n params.set(key, String(value));\n }\n return params.size > 0 ? params : undefined;\n}\n\nasync function readJiraResponseBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (text.length === 0) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction parseDynamicWebhookRegistration(\n body: JiraDynamicWebhookRegistrationResponse,\n): JiraDynamicWebhookRegistration {\n if (\n !body ||\n typeof body !== 'object' ||\n !Array.isArray(body.webhookRegistrationResult) ||\n body.webhookRegistrationResult.length !== 1\n ) {\n throw malformed('Jira webhook registration response did not contain exactly one result');\n }\n\n const result = body.webhookRegistrationResult[0];\n if (!result || typeof result !== 'object') {\n throw malformed('Jira webhook registration result was malformed');\n }\n const {createdWebhookId, errors} = result as {\n createdWebhookId?: unknown;\n errors?: unknown;\n };\n if (errors !== undefined && (!Array.isArray(errors) || errors.length > 0)) {\n throw malformed('Jira webhook registration returned errors');\n }\n if (\n typeof createdWebhookId !== 'number' ||\n !Number.isSafeInteger(createdWebhookId) ||\n createdWebhookId <= 0\n ) {\n throw malformed('Jira webhook registration did not return a created webhook id');\n }\n return {webhookId: createdWebhookId};\n}\n\nfunction parseAuthorization(body: JiraTokenResponse): JiraAuthorization {\n if (typeof body.access_token !== 'string' || body.access_token.length === 0) {\n throw malformed('Jira authorization response did not include an access token');\n }\n return {\n accessToken: body.access_token,\n refreshToken: typeof body.refresh_token === 'string' ? body.refresh_token : undefined,\n expiresAt: parseExpiresAt(body.expires_in),\n scopes: parseScopes(body.scope),\n };\n}\n\nfunction parseAccessibleResource(value: unknown): JiraAccessibleResource {\n if (!value || typeof value !== 'object') throw malformed('Jira site response was malformed');\n const {id, name, url, scopes} = value as JiraResourceResponse;\n if (\n typeof id !== 'string' ||\n id.length === 0 ||\n typeof name !== 'string' ||\n name.length === 0 ||\n typeof url !== 'string' ||\n url.length === 0 ||\n !Array.isArray(scopes) ||\n !scopes.every((scope) => typeof scope === 'string')\n ) {\n throw malformed('Jira site response did not include a valid cloud id, name, URL, and scopes');\n }\n return {cloudId: id, name, url, scopes};\n}\n\nfunction parseScopes(scope: unknown): string[] {\n if (typeof scope === 'string')\n return scope\n .split(SCOPE_SEPARATOR_RE)\n .map((value) => value.trim())\n .filter(Boolean);\n if (Array.isArray(scope) && scope.every((value) => typeof value === 'string')) return scope;\n if (scope === undefined) return [];\n throw malformed('Jira authorization response included malformed scopes');\n}\n\nfunction parseExpiresAt(expiresIn: unknown): Date | undefined {\n if (expiresIn === undefined) return undefined;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw malformed('Jira authorization response included a malformed expiry');\n }\n return new Date(Date.now() + expiresIn * 1000);\n}\n\nexport async function mapJiraError<T>(operation: string, request: () => Promise<T>): Promise<T> {\n try {\n return await request();\n } catch (error) {\n if (error instanceof JiraIntegrationProviderError) throw error;\n if (error instanceof HTTPError) {\n const {status, statusText, headers} = error.response;\n logger().warn({operation, status, statusText}, 'Jira API request rejected');\n if (status === 429) {\n throw new JiraIntegrationProviderError(\n 'rate-limited',\n 'Jira request was rate limited',\n retryAfterSeconds(headers),\n );\n }\n if (status >= 500)\n throw new JiraIntegrationProviderError('provider-unavailable', 'Jira request failed');\n if (status === 401 || status === 403) {\n throw new JiraIntegrationProviderError('access-denied', 'Jira request was rejected');\n }\n if (status === 400 && operation === 'refresh-access-token') {\n const errorCode = readJiraOAuthErrorCode(error.data);\n if (errorCode && JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES.has(errorCode)) {\n throw new JiraIntegrationProviderError(\n 'access-denied',\n 'Jira refresh token was rejected; reconnect is required',\n );\n }\n }\n throw malformed('Jira request was rejected');\n }\n if (error instanceof TimeoutError) {\n logger().warn({operation}, 'Jira API request timed out');\n throw new JiraIntegrationProviderError('timeout', 'Jira request timed out');\n }\n logger().warn(\n {operation, errName: error instanceof Error ? error.name : typeof error},\n 'Jira API request failed',\n );\n throw new JiraIntegrationProviderError('provider-unavailable', 'Jira request failed');\n }\n}\n\nfunction malformed(message: string): JiraIntegrationProviderError {\n return new JiraIntegrationProviderError('malformed-provider-response', message);\n}\n\nfunction readJiraOAuthErrorCode(data: unknown): string | undefined {\n if (!data || typeof data !== 'object') return undefined;\n const errorCode = (data as {error?: unknown}).error;\n return typeof errorCode === 'string' ? errorCode : undefined;\n}\n\nfunction retryAfterSeconds(headers: Headers): number | undefined {\n const value = headers.get('retry-after');\n if (!value) return undefined;\n const parsed = Number.parseInt(value, 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n"],"names":["jiraWebhookEventNames","logger","ky","HTTPError","TimeoutError","config","JiraIntegrationProviderError","JIRA_API_TIMEOUT_MS","SCOPE_SEPARATOR_RE","TRAILING_SLASHES_RE","JIRA_DYNAMIC_WEBHOOK_EVENTS","JIRA_DYNAMIC_WEBHOOK_JQL","JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES","Set","createJiraApiClient","exchangeAuthorizationCode","input","body","mapJiraError","post","JIRA_AUTH_BASE_URL","URLSearchParams","grant_type","client_id","JIRA_OAUTH_CLIENT_ID","client_secret","JIRA_OAUTH_CLIENT_SECRET","code","redirect_uri","JIRA_OAUTH_REDIRECT_URL","timeout","json","parseAuthorization","refreshAccessToken","refresh_token","refreshToken","getAccessibleResources","get","JIRA_API_BASE_URL","headers","authorization","accessToken","Array","isArray","malformed","map","parseAccessibleResource","getMyself","cloudId","accountId","length","registerDynamicWebhook","url","webhooks","events","jqlFilter","parseDynamicWebhookRegistration","deleteDynamicWebhook","delete","webhookIds","webhookId","createJiraAgentToolsClient","request","requestJiraRest","operation","response","jiraRestUrl","path","method","query","undefined","searchParams","jiraQueryParams","status","readJiraResponseBody","error","baseUrl","replace","normalizedPath","startsWith","encodeURIComponent","params","key","value","Object","entries","item","append","String","set","size","text","JSON","parse","webhookRegistrationResult","result","createdWebhookId","errors","Number","isSafeInteger","access_token","expiresAt","parseExpiresAt","expires_in","scopes","parseScopes","scope","id","name","every","split","trim","filter","Boolean","expiresIn","isFinite","Date","now","statusText","warn","retryAfterSeconds","errorCode","readJiraOAuthErrorCode","data","has","errName","Error","message","parsed","parseInt","isNaN"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentToolSession, AgentToolsProvider, IntegrationConnection, OpenAgentToolsSessionInput } from '@shipfox/api-integration-spi';
|
|
2
|
+
import type { JiraAgentToolsClient } from '#api/client.js';
|
|
3
|
+
import type { JiraTokenStore } from '#core/tokens.js';
|
|
4
|
+
import { type JiraAgentToolRequiredScope } from './agent-tools.js';
|
|
5
|
+
type JiraIntegrationConnection = IntegrationConnection<'jira'>;
|
|
6
|
+
export type JiraToolCallResult = {
|
|
7
|
+
isError?: boolean | undefined;
|
|
8
|
+
content: readonly {
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
}[];
|
|
12
|
+
structuredContent?: Record<string, unknown> | undefined;
|
|
13
|
+
};
|
|
14
|
+
export interface JiraAgentToolsProviderOptions {
|
|
15
|
+
jira: Pick<JiraAgentToolsClient, 'request'>;
|
|
16
|
+
tokenStore: Pick<JiraTokenStore, 'getAccessToken'>;
|
|
17
|
+
}
|
|
18
|
+
export declare class JiraAgentToolsProvider implements AgentToolsProvider<JiraIntegrationConnection, JiraAgentToolRequiredScope, unknown, JiraToolCallResult> {
|
|
19
|
+
private readonly options;
|
|
20
|
+
constructor(options: JiraAgentToolsProviderOptions);
|
|
21
|
+
catalog(): readonly [import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry];
|
|
22
|
+
selectionCatalog(): import("@shipfox/api-integration-spi").AgentToolSelectionCatalog;
|
|
23
|
+
openSession(input: OpenAgentToolsSessionInput<JiraIntegrationConnection, JiraAgentToolRequiredScope, unknown>): Promise<AgentToolSession<JiraToolCallResult>>;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=agent-tools-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools-provider.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAwB,oBAAoB,EAAC,MAAM,gBAAgB,CAAC;AAChF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAEL,KAAK,0BAA0B,EAGhC,MAAM,kBAAkB,CAAC;AAG1B,KAAK,yBAAyB,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAE/D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,SAAS;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD,CAAC;AAEF,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC;IAC5C,UAAU,EAAE,IAAI,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;CACpD;AAED,qBAAa,sBACX,YACE,kBAAkB,CAChB,yBAAyB,EACzB,0BAA0B,EAC1B,OAAO,EACP,kBAAkB,CACnB;IAES,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,6BAA6B;IAEnE,OAAO;IAIP,gBAAgB;IAIV,WAAW,CACf,KAAK,EAAE,0BAA0B,CAC/B,yBAAyB,EACzB,0BAA0B,EAC1B,OAAO,CACR,GACA,OAAO,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;CAsDjD"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { JIRA_TOOL_OPERATIONS, jiraAgentToolCatalog, jiraAgentToolSelectionCatalog } from './agent-tools.js';
|
|
2
|
+
import { JiraIntegrationProviderError } from './errors.js';
|
|
3
|
+
export class JiraAgentToolsProvider {
|
|
4
|
+
constructor(options){
|
|
5
|
+
this.options = options;
|
|
6
|
+
}
|
|
7
|
+
catalog() {
|
|
8
|
+
return jiraAgentToolCatalog;
|
|
9
|
+
}
|
|
10
|
+
selectionCatalog() {
|
|
11
|
+
return jiraAgentToolSelectionCatalog;
|
|
12
|
+
}
|
|
13
|
+
async openSession(input) {
|
|
14
|
+
const accessToken = await this.options.tokenStore.getAccessToken({
|
|
15
|
+
connectionId: input.connection.id
|
|
16
|
+
});
|
|
17
|
+
const cloudId = input.connection.externalAccountId;
|
|
18
|
+
return {
|
|
19
|
+
call: async (call)=>{
|
|
20
|
+
const tool = input.tools.find((candidate)=>candidate.id === call.toolId);
|
|
21
|
+
if (!tool) return jiraToolError(`Unknown Jira tool: ${call.toolId}`);
|
|
22
|
+
const operation = JIRA_TOOL_OPERATIONS[call.toolId];
|
|
23
|
+
if (!operation) return jiraToolError(`Unknown Jira tool: ${call.toolId}`);
|
|
24
|
+
const missingParameter = missingRequiredParameter(tool, call.arguments);
|
|
25
|
+
if (missingParameter) {
|
|
26
|
+
return jiraToolError(`Missing required parameter: ${missingParameter}`);
|
|
27
|
+
}
|
|
28
|
+
let response;
|
|
29
|
+
try {
|
|
30
|
+
response = await this.options.jira.request({
|
|
31
|
+
accessToken,
|
|
32
|
+
cloudId,
|
|
33
|
+
method: operation.method,
|
|
34
|
+
path: operation.path(call.arguments),
|
|
35
|
+
...operation.query === undefined ? {} : {
|
|
36
|
+
query: operation.query(call.arguments)
|
|
37
|
+
},
|
|
38
|
+
...operation.body === undefined ? {} : {
|
|
39
|
+
body: operation.body(call.arguments)
|
|
40
|
+
},
|
|
41
|
+
operation: call.toolId
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error instanceof JiraIntegrationProviderError) {
|
|
45
|
+
return jiraToolError(error.message, {
|
|
46
|
+
code: error.reason,
|
|
47
|
+
retryAfterSeconds: error.retryAfterSeconds
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
if (response.status === 400 || response.status === 404) {
|
|
53
|
+
return jiraRequestError(response.body, response.status === 404 ? 'Jira resource was not found' : 'Jira request was rejected');
|
|
54
|
+
}
|
|
55
|
+
if (response.status < 200 || response.status >= 300) {
|
|
56
|
+
return jiraToolError(`Jira request returned HTTP ${response.status}`);
|
|
57
|
+
}
|
|
58
|
+
return jiraToolResult(response.body, response.status);
|
|
59
|
+
},
|
|
60
|
+
close: ()=>Promise.resolve()
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function missingRequiredParameter(tool, args) {
|
|
65
|
+
const required = tool.inputSchema.required;
|
|
66
|
+
if (!Array.isArray(required)) return undefined;
|
|
67
|
+
return required.find((parameter)=>typeof parameter === 'string' && args[parameter] === undefined);
|
|
68
|
+
}
|
|
69
|
+
function jiraToolResult(body, status) {
|
|
70
|
+
const structuredContent = body === undefined ? {
|
|
71
|
+
status
|
|
72
|
+
} : isRecord(body) ? body : {
|
|
73
|
+
result: body
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: 'text',
|
|
79
|
+
text: JSON.stringify(structuredContent)
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
structuredContent
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function jiraRequestError(body, fallbackMessage) {
|
|
86
|
+
return jiraToolError(jiraErrorMessage(body, fallbackMessage));
|
|
87
|
+
}
|
|
88
|
+
function jiraErrorMessage(body, fallbackMessage) {
|
|
89
|
+
if (typeof body === 'string' && body.length > 0) return body;
|
|
90
|
+
if (!isRecord(body)) return fallbackMessage;
|
|
91
|
+
const errorMessages = body.errorMessages;
|
|
92
|
+
if (Array.isArray(errorMessages)) {
|
|
93
|
+
const message = errorMessages.filter((value)=>typeof value === 'string');
|
|
94
|
+
if (message.length > 0) return message.join('; ');
|
|
95
|
+
}
|
|
96
|
+
if (typeof body.message === 'string' && body.message.length > 0) return body.message;
|
|
97
|
+
const errors = body.errors;
|
|
98
|
+
if (isRecord(errors)) {
|
|
99
|
+
const messages = Object.entries(errors).filter((entry)=>typeof entry[1] === 'string');
|
|
100
|
+
if (messages.length > 0) return messages.map(([field, message])=>`${field}: ${message}`).join('; ');
|
|
101
|
+
}
|
|
102
|
+
return fallbackMessage;
|
|
103
|
+
}
|
|
104
|
+
function jiraToolError(message, options = {}) {
|
|
105
|
+
const structuredContent = {
|
|
106
|
+
...options.code === undefined ? {} : {
|
|
107
|
+
code: options.code
|
|
108
|
+
},
|
|
109
|
+
...options.retryAfterSeconds === undefined ? {} : {
|
|
110
|
+
retryAfterSeconds: options.retryAfterSeconds
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
isError: true,
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: message
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
...Object.keys(structuredContent).length === 0 ? {} : {
|
|
122
|
+
structuredContent
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function isRecord(value) {
|
|
127
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=agent-tools-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/agent-tools-provider.ts"],"sourcesContent":["import type {\n AgentToolCatalogEntry,\n AgentToolSession,\n AgentToolsProvider,\n IntegrationConnection,\n OpenAgentToolsSessionInput,\n} from '@shipfox/api-integration-spi';\nimport type {JiraAgentToolResponse, JiraAgentToolsClient} from '#api/client.js';\nimport type {JiraTokenStore} from '#core/tokens.js';\nimport {\n JIRA_TOOL_OPERATIONS,\n type JiraAgentToolRequiredScope,\n jiraAgentToolCatalog,\n jiraAgentToolSelectionCatalog,\n} from './agent-tools.js';\nimport {JiraIntegrationProviderError} from './errors.js';\n\ntype JiraIntegrationConnection = IntegrationConnection<'jira'>;\n\nexport type JiraToolCallResult = {\n isError?: boolean | undefined;\n content: readonly {type: 'text'; text: string}[];\n structuredContent?: Record<string, unknown> | undefined;\n};\n\nexport interface JiraAgentToolsProviderOptions {\n jira: Pick<JiraAgentToolsClient, 'request'>;\n tokenStore: Pick<JiraTokenStore, 'getAccessToken'>;\n}\n\nexport class JiraAgentToolsProvider\n implements\n AgentToolsProvider<\n JiraIntegrationConnection,\n JiraAgentToolRequiredScope,\n unknown,\n JiraToolCallResult\n >\n{\n constructor(private readonly options: JiraAgentToolsProviderOptions) {}\n\n catalog() {\n return jiraAgentToolCatalog;\n }\n\n selectionCatalog() {\n return jiraAgentToolSelectionCatalog;\n }\n\n async openSession(\n input: OpenAgentToolsSessionInput<\n JiraIntegrationConnection,\n JiraAgentToolRequiredScope,\n unknown\n >,\n ): Promise<AgentToolSession<JiraToolCallResult>> {\n const accessToken = await this.options.tokenStore.getAccessToken({\n connectionId: input.connection.id,\n });\n const cloudId = input.connection.externalAccountId;\n\n return {\n call: async (call) => {\n const tool = input.tools.find((candidate) => candidate.id === call.toolId);\n if (!tool) return jiraToolError(`Unknown Jira tool: ${call.toolId}`);\n\n const operation = JIRA_TOOL_OPERATIONS[call.toolId as keyof typeof JIRA_TOOL_OPERATIONS];\n if (!operation) return jiraToolError(`Unknown Jira tool: ${call.toolId}`);\n\n const missingParameter = missingRequiredParameter(tool, call.arguments);\n if (missingParameter) {\n return jiraToolError(`Missing required parameter: ${missingParameter}`);\n }\n\n let response: JiraAgentToolResponse;\n try {\n response = await this.options.jira.request({\n accessToken,\n cloudId,\n method: operation.method,\n path: operation.path(call.arguments),\n ...(operation.query === undefined ? {} : {query: operation.query(call.arguments)}),\n ...(operation.body === undefined ? {} : {body: operation.body(call.arguments)}),\n operation: call.toolId,\n });\n } catch (error) {\n if (error instanceof JiraIntegrationProviderError) {\n return jiraToolError(error.message, {\n code: error.reason,\n retryAfterSeconds: error.retryAfterSeconds,\n });\n }\n throw error;\n }\n\n if (response.status === 400 || response.status === 404) {\n return jiraRequestError(\n response.body,\n response.status === 404 ? 'Jira resource was not found' : 'Jira request was rejected',\n );\n }\n if (response.status < 200 || response.status >= 300) {\n return jiraToolError(`Jira request returned HTTP ${response.status}`);\n }\n return jiraToolResult(response.body, response.status);\n },\n close: () => Promise.resolve(),\n };\n }\n}\n\nfunction missingRequiredParameter(\n tool: AgentToolCatalogEntry<JiraAgentToolRequiredScope>,\n args: Record<string, unknown>,\n): string | undefined {\n const required = tool.inputSchema.required;\n if (!Array.isArray(required)) return undefined;\n return required.find(\n (parameter) => typeof parameter === 'string' && args[parameter] === undefined,\n );\n}\n\nfunction jiraToolResult(body: unknown, status: number): JiraToolCallResult {\n const structuredContent = body === undefined ? {status} : isRecord(body) ? body : {result: body};\n return {\n content: [{type: 'text', text: JSON.stringify(structuredContent)}],\n structuredContent,\n };\n}\n\nfunction jiraRequestError(body: unknown, fallbackMessage: string): JiraToolCallResult {\n return jiraToolError(jiraErrorMessage(body, fallbackMessage));\n}\n\nfunction jiraErrorMessage(body: unknown, fallbackMessage: string): string {\n if (typeof body === 'string' && body.length > 0) return body;\n if (!isRecord(body)) return fallbackMessage;\n\n const errorMessages = body.errorMessages;\n if (Array.isArray(errorMessages)) {\n const message = errorMessages.filter((value): value is string => typeof value === 'string');\n if (message.length > 0) return message.join('; ');\n }\n if (typeof body.message === 'string' && body.message.length > 0) return body.message;\n const errors = body.errors;\n if (isRecord(errors)) {\n const messages = Object.entries(errors).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n );\n if (messages.length > 0)\n return messages.map(([field, message]) => `${field}: ${message}`).join('; ');\n }\n return fallbackMessage;\n}\n\nfunction jiraToolError(\n message: string,\n options: {code?: string | undefined; retryAfterSeconds?: number | undefined} = {},\n): JiraToolCallResult {\n const structuredContent = {\n ...(options.code === undefined ? {} : {code: options.code}),\n ...(options.retryAfterSeconds === undefined\n ? {}\n : {retryAfterSeconds: options.retryAfterSeconds}),\n };\n return {\n isError: true,\n content: [{type: 'text', text: message}],\n ...(Object.keys(structuredContent).length === 0 ? {} : {structuredContent}),\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"],"names":["JIRA_TOOL_OPERATIONS","jiraAgentToolCatalog","jiraAgentToolSelectionCatalog","JiraIntegrationProviderError","JiraAgentToolsProvider","options","catalog","selectionCatalog","openSession","input","accessToken","tokenStore","getAccessToken","connectionId","connection","id","cloudId","externalAccountId","call","tool","tools","find","candidate","toolId","jiraToolError","operation","missingParameter","missingRequiredParameter","arguments","response","jira","request","method","path","query","undefined","body","error","message","code","reason","retryAfterSeconds","status","jiraRequestError","jiraToolResult","close","Promise","resolve","args","required","inputSchema","Array","isArray","parameter","structuredContent","isRecord","result","content","type","text","JSON","stringify","fallbackMessage","jiraErrorMessage","length","errorMessages","filter","value","join","errors","messages","Object","entries","entry","map","field","isError","keys"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JiraAgentToolId, JiraAgentToolRequiredScope, JiraAgentToolCatalogEntry as JiraDtoAgentToolCatalogEntry } from '@shipfox/api-integration-jira-dto';
|
|
2
|
+
import type { AgentToolSelectionCatalog } from '@shipfox/api-integration-spi';
|
|
3
|
+
import type { JiraAgentToolHttpMethod, JiraAgentToolQueryValue } from '#api/client.js';
|
|
4
|
+
export type JiraAgentToolCatalogEntry = JiraDtoAgentToolCatalogEntry;
|
|
5
|
+
export type { JiraAgentToolId, JiraAgentToolRequiredScope };
|
|
6
|
+
export declare const jiraAgentToolCatalog: readonly [JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry, JiraDtoAgentToolCatalogEntry];
|
|
7
|
+
export declare const jiraAgentToolSelectionCatalog: AgentToolSelectionCatalog;
|
|
8
|
+
export interface JiraToolOperation {
|
|
9
|
+
method: JiraAgentToolHttpMethod;
|
|
10
|
+
path: (args: Record<string, unknown>) => string;
|
|
11
|
+
query?: ((args: Record<string, unknown>) => Record<string, JiraAgentToolQueryValue>) | undefined;
|
|
12
|
+
body?: ((args: Record<string, unknown>) => unknown) | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare const JIRA_TOOL_OPERATIONS: Record<JiraAgentToolId, JiraToolOperation>;
|
|
15
|
+
export declare function jiraPlainTextToAdf(body: string): Record<string, unknown>;
|
|
16
|
+
//# sourceMappingURL=agent-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAC1B,yBAAyB,IAAI,4BAA4B,EAC1D,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAGV,yBAAyB,EAE1B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAC,uBAAuB,EAAE,uBAAuB,EAAC,MAAM,gBAAgB,CAAC;AAErF,MAAM,MAAM,yBAAyB,GAAG,4BAA4B,CAAC;AACrE,YAAY,EAAC,eAAe,EAAE,0BAA0B,EAAC,CAAC;AAkB1D,eAAO,MAAM,oBAAoB,qVAmMwB,CAAC;AAE1D,eAAO,MAAM,6BAA6B,2BACgB,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,uBAAuB,CAAC;IAChC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC;IAChD,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,GAAG,SAAS,CAAC;IACjG,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;CACjE;AAED,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,eAAe,EAAE,iBAAiB,CAmE3E,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWxE"}
|