@shipfox/api-agent-access 21.0.0 → 21.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 +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/core/log-tools.d.ts +10 -0
- package/dist/core/log-tools.d.ts.map +1 -0
- package/dist/core/log-tools.js +165 -0
- package/dist/core/log-tools.js.map +1 -0
- package/dist/core/paged-tools.d.ts.map +1 -1
- package/dist/core/paged-tools.js +8 -50
- package/dist/core/paged-tools.js.map +1 -1
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +57 -18
- package/dist/core/response.js.map +1 -1
- package/dist/core/tool-utils.d.ts +36 -0
- package/dist/core/tool-utils.d.ts.map +1 -0
- package/dist/core/tool-utils.js +68 -0
- package/dist/core/tool-utils.js.map +1 -0
- package/dist/core/workflow-diagnostic-tools.d.ts +5 -0
- package/dist/core/workflow-diagnostic-tools.d.ts.map +1 -0
- package/dist/core/workflow-diagnostic-tools.js +532 -0
- package/dist/core/workflow-diagnostic-tools.js.map +1 -0
- package/dist/core/workflow-tools.d.ts +4 -0
- package/dist/core/workflow-tools.d.ts.map +1 -0
- package/dist/core/workflow-tools.js +434 -0
- package/dist/core/workflow-tools.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/src/core/log-tools.test.ts +370 -0
- package/src/core/log-tools.ts +271 -0
- package/src/core/paged-tools.test.ts +19 -2
- package/src/core/paged-tools.ts +18 -67
- package/src/core/response.ts +67 -19
- package/src/core/tool-utils.ts +109 -0
- package/src/core/workflow-diagnostic-tools.test.ts +693 -0
- package/src/core/workflow-diagnostic-tools.ts +714 -0
- package/src/core/workflow-tools.test.ts +531 -0
- package/src/core/workflow-tools.ts +514 -0
- package/src/index.ts +5 -0
- package/src/presentation/mcp-server.test.ts +57 -1
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/core/paged-tools.ts
CHANGED
|
@@ -6,8 +6,6 @@ import {
|
|
|
6
6
|
AGENT_ACCESS_DIAGNOSTIC_MAX_ITEMS,
|
|
7
7
|
AGENT_ACCESS_DIAGNOSTIC_MESSAGE_MAX_BYTES,
|
|
8
8
|
AGENT_ACCESS_DIAGNOSTIC_PATH_MAX_BYTES,
|
|
9
|
-
AGENT_ACCESS_TEXT_MAX_BYTES,
|
|
10
|
-
type AgentAccessEnvelopeDto,
|
|
11
9
|
agentAccessOutputSchema,
|
|
12
10
|
type GetRunAnnotationsInputDto,
|
|
13
11
|
getRunAnnotationsInputJsonSchema,
|
|
@@ -45,17 +43,23 @@ import {
|
|
|
45
43
|
import type {TriggersInterModuleClient} from '@shipfox/api-triggers-dto/inter-module';
|
|
46
44
|
import type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';
|
|
47
45
|
import {isInterModuleKnownError} from '@shipfox/inter-module';
|
|
46
|
+
import {encodeNumberIdCursor, encodeStringIdCursor} from '@shipfox/node-drizzle';
|
|
47
|
+
import {agentAccessSuccess} from './envelope.js';
|
|
48
48
|
import {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
cap,
|
|
50
|
+
capNullable,
|
|
51
|
+
decodeNumberCursor,
|
|
52
|
+
decodeStringCursor,
|
|
53
|
+
decodeTimestampCursor,
|
|
54
|
+
encodeTimestampCursor,
|
|
55
|
+
invalidRequest,
|
|
56
|
+
notFound,
|
|
57
|
+
parseInput,
|
|
58
|
+
reducePage,
|
|
59
|
+
truncateAgentAccessUtf8,
|
|
60
|
+
} from './tool-utils.js';
|
|
58
61
|
import type {AgentAccessTool} from './tools.js';
|
|
62
|
+
import {createAgentAccessWorkflowTools} from './workflow-tools.js';
|
|
59
63
|
|
|
60
64
|
export interface AgentAccessPagedToolsOptions {
|
|
61
65
|
projects: ProjectsModuleClient;
|
|
@@ -72,6 +76,7 @@ export function createAgentAccessTools(
|
|
|
72
76
|
createListProjectsTool(options.projects),
|
|
73
77
|
createListWorkflowDefinitionsTool(options.definitions),
|
|
74
78
|
createListWorkflowRunsTool(options.projects, options.workflows),
|
|
79
|
+
...createAgentAccessWorkflowTools(options.workflows),
|
|
75
80
|
createGetRunAnnotationsTool(options.workflows, options.annotations),
|
|
76
81
|
createListTriggerEventsTool(options.triggers),
|
|
77
82
|
];
|
|
@@ -316,12 +321,12 @@ async function resolveRunAttempt(
|
|
|
316
321
|
).attempt;
|
|
317
322
|
}
|
|
318
323
|
|
|
319
|
-
const
|
|
324
|
+
const overview = await workflows.getWorkflowRunOverview({
|
|
320
325
|
workspaceId: context.workspaceId,
|
|
321
326
|
workflowRunId: input.run_id,
|
|
322
327
|
attempt: input.attempt,
|
|
323
328
|
});
|
|
324
|
-
return
|
|
329
|
+
return overview === null ? null : input.attempt;
|
|
325
330
|
}
|
|
326
331
|
|
|
327
332
|
function toProjectResult(project: {
|
|
@@ -578,15 +583,6 @@ function triggerEventFilters(input: ListTriggerEventsInputDto) {
|
|
|
578
583
|
};
|
|
579
584
|
}
|
|
580
585
|
|
|
581
|
-
function reducePage(
|
|
582
|
-
envelope: AgentAccessEnvelopeDto,
|
|
583
|
-
itemKey: string,
|
|
584
|
-
items: readonly Record<string, unknown>[],
|
|
585
|
-
cursorForItem: (item: Record<string, unknown>, index: number) => string,
|
|
586
|
-
): AgentAccessEnvelopeDto {
|
|
587
|
-
return reducePagedAgentAccessResponse({envelope, itemKey, items, cursorForItem});
|
|
588
|
-
}
|
|
589
|
-
|
|
590
586
|
function projectCursor(item: Record<string, unknown>): string {
|
|
591
587
|
return encodeTimestampCursor(String(item.created_at), String(item.id));
|
|
592
588
|
}
|
|
@@ -610,48 +606,3 @@ function annotationCursor(item: Record<string, unknown>): string {
|
|
|
610
606
|
function triggerEventCursor(item: Record<string, unknown>): string {
|
|
611
607
|
return encodeTimestampCursor(String(item.received_at), String(item.id));
|
|
612
608
|
}
|
|
613
|
-
|
|
614
|
-
function decodeTimestampCursor(
|
|
615
|
-
value: string | undefined,
|
|
616
|
-
): {createdAt: string; id: string} | undefined {
|
|
617
|
-
if (value === undefined) return undefined;
|
|
618
|
-
const cursor = decodeTimestampIdCursor(value);
|
|
619
|
-
return cursor ? {createdAt: cursor.createdAt.toISOString(), id: cursor.id} : undefined;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function decodeStringCursor(value: string | undefined): {value: string; id: string} | undefined {
|
|
623
|
-
return value === undefined ? undefined : decodeStringIdCursor(value);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function decodeNumberCursor(value: string | undefined): {value: number; id: string} | undefined {
|
|
627
|
-
return value === undefined ? undefined : decodeNumberIdCursor(value);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
function encodeTimestampCursor(createdAt: string, id: string): string {
|
|
631
|
-
return encodeTimestampIdCursor({createdAt: new Date(createdAt), id});
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function cap(value: string, maxBytes = AGENT_ACCESS_TEXT_MAX_BYTES): string {
|
|
635
|
-
return truncateAgentAccessUtf8(value, maxBytes).value;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function capNullable(value: string | null, maxBytes = AGENT_ACCESS_TEXT_MAX_BYTES): string | null {
|
|
639
|
-
return value === null ? null : cap(value, maxBytes);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
function invalidRequest(): AgentAccessEnvelopeDto {
|
|
643
|
-
return agentAccessError('invalid-request');
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function notFound(): AgentAccessEnvelopeDto {
|
|
647
|
-
return agentAccessError('not-found');
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
interface SafeParseSchema<T> {
|
|
651
|
-
safeParse(value: unknown): {success: true; data: T} | {success: false};
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function parseInput<T>(schema: SafeParseSchema<T>, value: unknown): T | undefined {
|
|
655
|
-
const parsed = schema.safeParse(value);
|
|
656
|
-
return parsed.success ? parsed.data : undefined;
|
|
657
|
-
}
|
package/src/core/response.ts
CHANGED
|
@@ -46,6 +46,13 @@ export interface ReducePagedAgentAccessResponseParams {
|
|
|
46
46
|
maxBytes?: number | undefined;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
interface PagedResponseFitState {
|
|
50
|
+
emptyCandidate: AgentAccessEnvelopeDto;
|
|
51
|
+
emptyResult: Record<string, unknown>;
|
|
52
|
+
candidateBytes: readonly number[];
|
|
53
|
+
itemCursors: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
/**
|
|
50
57
|
* Fits a paged success response without reusing a producer cursor that points past dropped rows.
|
|
51
58
|
* The cursor is always rebuilt from the final retained item.
|
|
@@ -60,33 +67,68 @@ export function reducePagedAgentAccessResponse(
|
|
|
60
67
|
return agentAccessError('content-too-large');
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const retained = params.items.slice(0, itemCount);
|
|
72
|
-
const last = retained.at(-1);
|
|
73
|
-
const nextCursor = last === undefined ? null : params.cursorForItem(last, itemCount - 1);
|
|
74
|
-
const candidate: AgentAccessEnvelopeDto = {
|
|
75
|
-
...params.envelope,
|
|
70
|
+
const producerResult = params.envelope.result;
|
|
71
|
+
const fitState = buildPagedResponseFitState(params, producerResult, initialBytes);
|
|
72
|
+
const itemCount = largestFittingItemCount(fitState.candidateBytes, params.items.length, maxBytes);
|
|
73
|
+
if (itemCount !== undefined) {
|
|
74
|
+
const nextCursor = itemCount === 0 ? null : fitState.itemCursors[itemCount - 1];
|
|
75
|
+
if (nextCursor === undefined && itemCount > 0) return agentAccessError('content-too-large');
|
|
76
|
+
return {
|
|
77
|
+
...fitState.emptyCandidate,
|
|
76
78
|
result: {
|
|
77
|
-
...
|
|
78
|
-
[params.itemKey]:
|
|
79
|
-
next_cursor: nextCursor,
|
|
79
|
+
...fitState.emptyResult,
|
|
80
|
+
[params.itemKey]: params.items.slice(0, itemCount),
|
|
81
|
+
next_cursor: nextCursor ?? null,
|
|
80
82
|
},
|
|
81
|
-
response_truncated: true,
|
|
82
|
-
response_total_bytes: initialBytes,
|
|
83
83
|
};
|
|
84
|
-
if (serializedAgentAccessEnvelopeByteLength(candidate) <= maxBytes) return candidate;
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
return agentAccessError('content-too-large');
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
function buildPagedResponseFitState(
|
|
90
|
+
params: ReducePagedAgentAccessResponseParams,
|
|
91
|
+
producerResult: Record<string, unknown>,
|
|
92
|
+
initialBytes: number,
|
|
93
|
+
): PagedResponseFitState {
|
|
94
|
+
const emptyResult = {...producerResult, [params.itemKey]: [], next_cursor: null};
|
|
95
|
+
const emptyCandidate: AgentAccessEnvelopeDto = {
|
|
96
|
+
...params.envelope,
|
|
97
|
+
result: emptyResult,
|
|
98
|
+
response_truncated: true,
|
|
99
|
+
response_total_bytes: initialBytes,
|
|
100
|
+
};
|
|
101
|
+
const emptyCandidateBytes = serializedAgentAccessEnvelopeByteLength(emptyCandidate);
|
|
102
|
+
const nullCursorBytes = serializedJsonByteLength(null);
|
|
103
|
+
const candidateBytes: number[] = [emptyCandidateBytes];
|
|
104
|
+
const itemCursors: string[] = [];
|
|
105
|
+
let retainedItemBytes = 0;
|
|
106
|
+
|
|
107
|
+
for (const [index, item] of params.items.entries()) {
|
|
108
|
+
retainedItemBytes += serializedJsonByteLength(item) + (index === 0 ? 0 : 1);
|
|
109
|
+
const cursor = params.cursorForItem(item, index);
|
|
110
|
+
itemCursors.push(cursor);
|
|
111
|
+
candidateBytes.push(
|
|
112
|
+
emptyCandidateBytes + retainedItemBytes + serializedJsonByteLength(cursor) - nullCursorBytes,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {emptyCandidate, emptyResult, candidateBytes, itemCursors};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function largestFittingItemCount(
|
|
120
|
+
candidateBytes: readonly number[],
|
|
121
|
+
itemCount: number,
|
|
122
|
+
maxBytes: number,
|
|
123
|
+
): number | undefined {
|
|
124
|
+
const minimumItemCount = itemCount === 0 ? 0 : 1;
|
|
125
|
+
for (let count = itemCount; count >= minimumItemCount; count -= 1) {
|
|
126
|
+
const candidateByteLength = candidateBytes[count];
|
|
127
|
+
if (candidateByteLength !== undefined && candidateByteLength <= maxBytes) return count;
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
90
132
|
export function fitAgentAccessResponseToCeiling(
|
|
91
133
|
envelope: AgentAccessEnvelopeDto,
|
|
92
134
|
maxBytes = AGENT_ACCESS_RESPONSE_MAX_BYTES,
|
|
@@ -99,3 +141,9 @@ export function fitAgentAccessResponseToCeiling(
|
|
|
99
141
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
100
142
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
101
143
|
}
|
|
144
|
+
|
|
145
|
+
function serializedJsonByteLength(value: unknown): number {
|
|
146
|
+
const serialized = JSON.stringify(value);
|
|
147
|
+
if (serialized === undefined) throw new Error('Agent-access value is not serializable');
|
|
148
|
+
return utf8Encoder.encode(serialized).byteLength;
|
|
149
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENT_ACCESS_TEXT_MAX_BYTES,
|
|
3
|
+
type AgentAccessEnvelopeDto,
|
|
4
|
+
} from '@shipfox/api-agent-access-dto';
|
|
5
|
+
import {
|
|
6
|
+
decodeNumberIdCursor,
|
|
7
|
+
decodeStringIdCursor,
|
|
8
|
+
decodeTimestampIdCursor,
|
|
9
|
+
encodeTimestampIdCursor,
|
|
10
|
+
} from '@shipfox/node-drizzle';
|
|
11
|
+
import {agentAccessError} from './envelope.js';
|
|
12
|
+
import {reducePagedAgentAccessResponse, truncateAgentAccessUtf8} from './response.js';
|
|
13
|
+
|
|
14
|
+
export {truncateAgentAccessUtf8};
|
|
15
|
+
|
|
16
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
17
|
+
const DECIMAL_RE = /^\d+$/u;
|
|
18
|
+
|
|
19
|
+
export interface SafeParseSchema<T> {
|
|
20
|
+
safeParse(value: unknown): {success: true; data: T} | {success: false};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseInput<T>(schema: SafeParseSchema<T>, value: unknown): T | undefined {
|
|
24
|
+
const parsed = schema.safeParse(value);
|
|
25
|
+
return parsed.success ? parsed.data : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function reducePage(
|
|
29
|
+
envelope: AgentAccessEnvelopeDto,
|
|
30
|
+
itemKey: string,
|
|
31
|
+
items: readonly Record<string, unknown>[],
|
|
32
|
+
cursorForItem: (item: Record<string, unknown>, index: number) => string,
|
|
33
|
+
): AgentAccessEnvelopeDto {
|
|
34
|
+
return reducePagedAgentAccessResponse({envelope, itemKey, items, cursorForItem});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function decodeTimestampCursor(
|
|
38
|
+
value: string | undefined,
|
|
39
|
+
): {createdAt: string; id: string} | undefined {
|
|
40
|
+
if (value === undefined) return undefined;
|
|
41
|
+
const cursor = decodeTimestampIdCursor(value);
|
|
42
|
+
return cursor ? {createdAt: cursor.createdAt.toISOString(), id: cursor.id} : undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function decodeStringCursor(
|
|
46
|
+
value: string | undefined,
|
|
47
|
+
): {value: string; id: string} | undefined {
|
|
48
|
+
return value === undefined ? undefined : decodeStringIdCursor(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function decodeNumberCursor(
|
|
52
|
+
value: string | undefined,
|
|
53
|
+
): {value: number; id: string} | undefined {
|
|
54
|
+
const cursor = value === undefined ? undefined : decodeNumberIdCursor(value);
|
|
55
|
+
return cursor !== undefined && Number.isSafeInteger(cursor.value) ? cursor : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function validateBoundedNumberCursor(
|
|
59
|
+
value: string | undefined,
|
|
60
|
+
bounds: {minValue: number; maxValue: number},
|
|
61
|
+
): string | undefined {
|
|
62
|
+
if (value === undefined) return undefined;
|
|
63
|
+
const cursor = decodeNumberCursor(value);
|
|
64
|
+
return cursor !== undefined &&
|
|
65
|
+
UUID_RE.test(cursor.id) &&
|
|
66
|
+
Number.isSafeInteger(cursor.value) &&
|
|
67
|
+
cursor.value >= bounds.minValue &&
|
|
68
|
+
cursor.value <= bounds.maxValue
|
|
69
|
+
? value
|
|
70
|
+
: undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function validateBoundedPositionCursor(
|
|
74
|
+
value: string | undefined,
|
|
75
|
+
maxValue: number,
|
|
76
|
+
): string | undefined {
|
|
77
|
+
if (value === undefined) return undefined;
|
|
78
|
+
const cursor = decodeStringCursor(value);
|
|
79
|
+
if (cursor === undefined || !UUID_RE.test(cursor.id) || !DECIMAL_RE.test(cursor.value)) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const position = Number(cursor.value);
|
|
83
|
+
return Number.isSafeInteger(position) && position >= 0 && position <= maxValue
|
|
84
|
+
? value
|
|
85
|
+
: undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function encodeTimestampCursor(createdAt: string, id: string): string {
|
|
89
|
+
return encodeTimestampIdCursor({createdAt: new Date(createdAt), id});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function cap(value: string, maxBytes = AGENT_ACCESS_TEXT_MAX_BYTES): string {
|
|
93
|
+
return truncateAgentAccessUtf8(value, maxBytes).value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function capNullable(
|
|
97
|
+
value: string | null,
|
|
98
|
+
maxBytes = AGENT_ACCESS_TEXT_MAX_BYTES,
|
|
99
|
+
): string | null {
|
|
100
|
+
return value === null ? null : cap(value, maxBytes);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function invalidRequest(): AgentAccessEnvelopeDto {
|
|
104
|
+
return agentAccessError('invalid-request');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function notFound(): AgentAccessEnvelopeDto {
|
|
108
|
+
return agentAccessError('not-found');
|
|
109
|
+
}
|