@valon-technologies/gestalt 0.0.1-alpha.13 → 0.0.1-alpha.14
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/gen/google/rpc/status_pb.ts +76 -0
- package/gen/v1/agent_pb.ts +561 -65
- package/gen/v1/datastore_pb.ts +457 -2
- package/gen/v1/plugin_pb.ts +31 -14
- package/gen/v1/pluginruntime_pb.ts +120 -81
- package/gen/v1/runtime_pb.ts +29 -1
- package/gen/v1/s3_pb.ts +101 -1
- package/gen/v1/workflow_pb.ts +644 -58
- package/package.json +5 -3
- package/src/agent.ts +168 -15
- package/src/api.ts +4 -1
- package/src/index.ts +69 -18
- package/src/indexeddb.ts +481 -1
- package/src/invoker.ts +3 -0
- package/src/plugin.ts +3 -184
- package/src/pluginruntime.ts +220 -0
- package/src/provider-kind.ts +6 -0
- package/src/provider.ts +16 -0
- package/src/runtime-log-host.ts +244 -0
- package/src/runtime.ts +44 -26
- package/src/s3.ts +81 -0
- package/src/telemetry.ts +429 -0
- package/src/workflow-manager.ts +11 -0
- package/src/manifest-metadata.ts +0 -107
package/src/telemetry.ts
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SpanKind,
|
|
3
|
+
SpanStatusCode,
|
|
4
|
+
ValueType,
|
|
5
|
+
context,
|
|
6
|
+
metrics,
|
|
7
|
+
trace,
|
|
8
|
+
type AttributeValue,
|
|
9
|
+
type Attributes,
|
|
10
|
+
type Exception,
|
|
11
|
+
type Histogram,
|
|
12
|
+
type Span,
|
|
13
|
+
} from "@opentelemetry/api";
|
|
14
|
+
|
|
15
|
+
/** OpenTelemetry instrumentation scope used by the Gestalt SDK. */
|
|
16
|
+
export const TELEMETRY_INSTRUMENTATION_NAME = "gestalt.provider";
|
|
17
|
+
/** Default GenAI provider name used for Gestalt-owned agent and tool work. */
|
|
18
|
+
export const GENAI_PROVIDER_NAME = "gestalt";
|
|
19
|
+
|
|
20
|
+
export const GENAI_OPERATION_CHAT = "chat";
|
|
21
|
+
export const GENAI_OPERATION_EXECUTE_TOOL = "execute_tool";
|
|
22
|
+
export const GENAI_OPERATION_INVOKE_AGENT = "invoke_agent";
|
|
23
|
+
|
|
24
|
+
export const GENAI_TOOL_TYPE_DATASTORE = "datastore";
|
|
25
|
+
export const GENAI_TOOL_TYPE_EXTENSION = "extension";
|
|
26
|
+
|
|
27
|
+
const OPERATION_DURATION_METRIC = "gen_ai.client.operation.duration";
|
|
28
|
+
const TOKEN_USAGE_METRIC = "gen_ai.client.token.usage";
|
|
29
|
+
const OPERATION_DURATION_BUCKETS = [
|
|
30
|
+
0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24,
|
|
31
|
+
20.48, 40.96, 81.92,
|
|
32
|
+
];
|
|
33
|
+
const TOKEN_USAGE_BUCKETS = [
|
|
34
|
+
1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304,
|
|
35
|
+
16777216, 67108864,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
let telemetryInstruments:
|
|
39
|
+
| { operationDuration: Histogram; tokenUsage: Histogram }
|
|
40
|
+
| undefined;
|
|
41
|
+
|
|
42
|
+
function getTelemetryInstruments(): {
|
|
43
|
+
operationDuration: Histogram;
|
|
44
|
+
tokenUsage: Histogram;
|
|
45
|
+
} {
|
|
46
|
+
if (telemetryInstruments === undefined) {
|
|
47
|
+
const meter = metrics.getMeter(TELEMETRY_INSTRUMENTATION_NAME);
|
|
48
|
+
telemetryInstruments = {
|
|
49
|
+
operationDuration: meter.createHistogram(OPERATION_DURATION_METRIC, {
|
|
50
|
+
description: "GenAI operation duration.",
|
|
51
|
+
unit: "s",
|
|
52
|
+
advice: { explicitBucketBoundaries: OPERATION_DURATION_BUCKETS },
|
|
53
|
+
}),
|
|
54
|
+
tokenUsage: meter.createHistogram(TOKEN_USAGE_METRIC, {
|
|
55
|
+
description: "Number of input and output tokens used.",
|
|
56
|
+
unit: "{token}",
|
|
57
|
+
valueType: ValueType.INT,
|
|
58
|
+
advice: { explicitBucketBoundaries: TOKEN_USAGE_BUCKETS },
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return telemetryInstruments;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Options for recording an upstream model SDK call. */
|
|
66
|
+
export interface ModelOperationOptions {
|
|
67
|
+
providerName: string;
|
|
68
|
+
requestModel: string;
|
|
69
|
+
requestOptions?: Record<string, unknown>;
|
|
70
|
+
requestAttributes?: Attributes;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Options for recording provider-owned agent turn execution. */
|
|
74
|
+
export interface AgentInvocationOptions {
|
|
75
|
+
agentName: string;
|
|
76
|
+
sessionId: string;
|
|
77
|
+
turnId: string;
|
|
78
|
+
model: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Options for recording provider-owned tool execution. */
|
|
82
|
+
export interface ToolExecutionOptions {
|
|
83
|
+
toolName: string;
|
|
84
|
+
toolCallId?: string;
|
|
85
|
+
toolType?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** GenAI token usage recorded on spans and token usage metrics. */
|
|
89
|
+
export interface TokenUsage {
|
|
90
|
+
inputTokens?: number;
|
|
91
|
+
outputTokens?: number;
|
|
92
|
+
cacheCreationInputTokens?: number;
|
|
93
|
+
cacheReadInputTokens?: number;
|
|
94
|
+
reasoningOutputTokens?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Records a GenAI span plus operation duration and token usage metrics. */
|
|
98
|
+
export class GenAIOperation {
|
|
99
|
+
readonly #span: Span;
|
|
100
|
+
readonly #startedAt = performance.now();
|
|
101
|
+
#metricAttributes: Attributes;
|
|
102
|
+
#errorType = "";
|
|
103
|
+
#ended = false;
|
|
104
|
+
|
|
105
|
+
constructor(span: Span, metricAttributes: Attributes) {
|
|
106
|
+
this.#span = span;
|
|
107
|
+
this.#metricAttributes = cleanAttributes(metricAttributes);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Ends the span and records operation duration. */
|
|
111
|
+
end(error?: unknown): void {
|
|
112
|
+
if (this.#ended) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (error !== undefined && error !== null) {
|
|
116
|
+
this.markError(errorType(error), errorMessage(error), error);
|
|
117
|
+
}
|
|
118
|
+
this.#ended = true;
|
|
119
|
+
|
|
120
|
+
const metricAttributes = { ...this.#metricAttributes };
|
|
121
|
+
if (this.#errorType !== "") {
|
|
122
|
+
metricAttributes["error.type"] = this.#errorType;
|
|
123
|
+
}
|
|
124
|
+
getTelemetryInstruments().operationDuration.record(
|
|
125
|
+
Math.max(0, (performance.now() - this.#startedAt) / 1000),
|
|
126
|
+
metricAttributes,
|
|
127
|
+
);
|
|
128
|
+
this.#span.end();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Marks the operation span and duration metric as failed. */
|
|
132
|
+
markError(
|
|
133
|
+
errorTypeValue: string,
|
|
134
|
+
description = "",
|
|
135
|
+
exception?: unknown,
|
|
136
|
+
): void {
|
|
137
|
+
this.#errorType = cleanString(errorTypeValue) || "_OTHER";
|
|
138
|
+
this.setAttribute("error.type", this.#errorType);
|
|
139
|
+
this.#metricAttributes["error.type"] = this.#errorType;
|
|
140
|
+
if (exception !== undefined && exception !== null) {
|
|
141
|
+
this.#span.recordException(exceptionForSpan(exception));
|
|
142
|
+
}
|
|
143
|
+
this.#span.setStatus({
|
|
144
|
+
code: SpanStatusCode.ERROR,
|
|
145
|
+
message: description || this.#errorType,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Sets a span attribute when the value is valid for OpenTelemetry. */
|
|
150
|
+
setAttribute(key: string, value: unknown): void {
|
|
151
|
+
const attrValue = toAttributeValue(value);
|
|
152
|
+
if (attrValue === undefined) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.#span.setAttribute(key, attrValue);
|
|
156
|
+
if (key === "gen_ai.response.model") {
|
|
157
|
+
this.#metricAttributes[key] = attrValue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Attaches common GenAI response metadata to the span. */
|
|
162
|
+
setResponseMetadata(input: {
|
|
163
|
+
id?: string;
|
|
164
|
+
model?: string;
|
|
165
|
+
finishReasons?: string[];
|
|
166
|
+
}): void {
|
|
167
|
+
this.setAttribute("gen_ai.response.id", input.id);
|
|
168
|
+
this.setAttribute("gen_ai.response.model", input.model);
|
|
169
|
+
if (input.finishReasons !== undefined) {
|
|
170
|
+
this.setAttribute("gen_ai.response.finish_reasons", input.finishReasons);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Records GenAI token usage on the span and token usage metric. */
|
|
175
|
+
recordUsage(usage: TokenUsage): void {
|
|
176
|
+
const inputTokens = tokenCount(usage.inputTokens);
|
|
177
|
+
const outputTokens = tokenCount(usage.outputTokens);
|
|
178
|
+
const cacheCreationInputTokens = tokenCount(usage.cacheCreationInputTokens);
|
|
179
|
+
const cacheReadInputTokens = tokenCount(usage.cacheReadInputTokens);
|
|
180
|
+
const reasoningOutputTokens = tokenCount(usage.reasoningOutputTokens);
|
|
181
|
+
|
|
182
|
+
this.setAttribute("gen_ai.usage.input_tokens", inputTokens);
|
|
183
|
+
this.setAttribute("gen_ai.usage.output_tokens", outputTokens);
|
|
184
|
+
this.setAttribute(
|
|
185
|
+
"gen_ai.usage.cache_creation.input_tokens",
|
|
186
|
+
cacheCreationInputTokens,
|
|
187
|
+
);
|
|
188
|
+
this.setAttribute(
|
|
189
|
+
"gen_ai.usage.cache_read.input_tokens",
|
|
190
|
+
cacheReadInputTokens,
|
|
191
|
+
);
|
|
192
|
+
this.setAttribute(
|
|
193
|
+
"gen_ai.usage.reasoning.output_tokens",
|
|
194
|
+
reasoningOutputTokens,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
this.#recordTokenUsage(inputTokens, "input");
|
|
198
|
+
this.#recordTokenUsage(outputTokens, "output");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#recordTokenUsage(tokens: number | undefined, tokenType: string): void {
|
|
202
|
+
if (tokens === undefined) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
getTelemetryInstruments().tokenUsage.record(tokens, {
|
|
206
|
+
...this.#metricAttributes,
|
|
207
|
+
"gen_ai.token.type": tokenType,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Runs a callback inside a GenAI model operation span. */
|
|
213
|
+
export async function withModelOperation<T>(
|
|
214
|
+
options: ModelOperationOptions,
|
|
215
|
+
callback: (operation: GenAIOperation) => T | Promise<T>,
|
|
216
|
+
): Promise<T> {
|
|
217
|
+
const providerName = cleanString(options.providerName) || "_OTHER";
|
|
218
|
+
const requestModel = cleanString(options.requestModel);
|
|
219
|
+
const metricAttributes = cleanAttributes({
|
|
220
|
+
"gen_ai.operation.name": GENAI_OPERATION_CHAT,
|
|
221
|
+
"gen_ai.provider.name": providerName,
|
|
222
|
+
"gen_ai.request.model": requestModel,
|
|
223
|
+
});
|
|
224
|
+
const spanAttributes = cleanAttributes({
|
|
225
|
+
...metricAttributes,
|
|
226
|
+
...requestOptionAttributes(options.requestOptions),
|
|
227
|
+
...options.requestAttributes,
|
|
228
|
+
});
|
|
229
|
+
return withOperation(
|
|
230
|
+
spanName(GENAI_OPERATION_CHAT, requestModel),
|
|
231
|
+
SpanKind.CLIENT,
|
|
232
|
+
spanAttributes,
|
|
233
|
+
metricAttributes,
|
|
234
|
+
callback,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Runs a callback inside a GenAI agent invocation span. */
|
|
239
|
+
export async function withAgentInvocation<T>(
|
|
240
|
+
options: AgentInvocationOptions,
|
|
241
|
+
callback: (operation: GenAIOperation) => T | Promise<T>,
|
|
242
|
+
): Promise<T> {
|
|
243
|
+
const agentName = cleanString(options.agentName) || "provider";
|
|
244
|
+
const model = cleanString(options.model);
|
|
245
|
+
return withOperation(
|
|
246
|
+
spanName(GENAI_OPERATION_INVOKE_AGENT, agentName),
|
|
247
|
+
SpanKind.INTERNAL,
|
|
248
|
+
cleanAttributes({
|
|
249
|
+
"gen_ai.operation.name": GENAI_OPERATION_INVOKE_AGENT,
|
|
250
|
+
"gen_ai.provider.name": GENAI_PROVIDER_NAME,
|
|
251
|
+
"gen_ai.agent.name": agentName,
|
|
252
|
+
"gen_ai.conversation.id": cleanString(options.sessionId),
|
|
253
|
+
"gen_ai.request.model": model,
|
|
254
|
+
"gestalt.agent.turn_id": cleanString(options.turnId),
|
|
255
|
+
}),
|
|
256
|
+
cleanAttributes({
|
|
257
|
+
"gen_ai.operation.name": GENAI_OPERATION_INVOKE_AGENT,
|
|
258
|
+
"gen_ai.provider.name": GENAI_PROVIDER_NAME,
|
|
259
|
+
"gen_ai.agent.name": agentName,
|
|
260
|
+
"gen_ai.request.model": model,
|
|
261
|
+
}),
|
|
262
|
+
callback,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Runs a callback inside a GenAI tool execution span. */
|
|
267
|
+
export async function withToolExecution<T>(
|
|
268
|
+
options: ToolExecutionOptions,
|
|
269
|
+
callback: (operation: GenAIOperation) => T | Promise<T>,
|
|
270
|
+
): Promise<T> {
|
|
271
|
+
const toolName = cleanString(options.toolName) || "_OTHER";
|
|
272
|
+
const toolType = cleanString(options.toolType) || GENAI_TOOL_TYPE_EXTENSION;
|
|
273
|
+
return withOperation(
|
|
274
|
+
spanName(GENAI_OPERATION_EXECUTE_TOOL, toolName),
|
|
275
|
+
SpanKind.INTERNAL,
|
|
276
|
+
cleanAttributes({
|
|
277
|
+
"gen_ai.operation.name": GENAI_OPERATION_EXECUTE_TOOL,
|
|
278
|
+
"gen_ai.provider.name": GENAI_PROVIDER_NAME,
|
|
279
|
+
"gen_ai.tool.name": toolName,
|
|
280
|
+
"gen_ai.tool.call.id": cleanString(options.toolCallId),
|
|
281
|
+
"gen_ai.tool.type": toolType,
|
|
282
|
+
}),
|
|
283
|
+
cleanAttributes({
|
|
284
|
+
"gen_ai.operation.name": GENAI_OPERATION_EXECUTE_TOOL,
|
|
285
|
+
"gen_ai.provider.name": GENAI_PROVIDER_NAME,
|
|
286
|
+
"gen_ai.tool.name": toolName,
|
|
287
|
+
"gen_ai.tool.type": toolType,
|
|
288
|
+
}),
|
|
289
|
+
callback,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function withOperation<T>(
|
|
294
|
+
name: string,
|
|
295
|
+
kind: SpanKind,
|
|
296
|
+
spanAttributes: Attributes,
|
|
297
|
+
metricAttributes: Attributes,
|
|
298
|
+
callback: (operation: GenAIOperation) => T | Promise<T>,
|
|
299
|
+
): Promise<T> {
|
|
300
|
+
const span = trace
|
|
301
|
+
.getTracer(TELEMETRY_INSTRUMENTATION_NAME)
|
|
302
|
+
.startSpan(name, { attributes: spanAttributes, kind });
|
|
303
|
+
const operation = new GenAIOperation(span, metricAttributes);
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
return await context.with(
|
|
307
|
+
trace.setSpan(context.active(), span),
|
|
308
|
+
async () => await callback(operation),
|
|
309
|
+
);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
operation.markError(errorType(error), errorMessage(error), error);
|
|
312
|
+
throw error;
|
|
313
|
+
} finally {
|
|
314
|
+
operation.end();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function requestOptionAttributes(
|
|
319
|
+
options: Record<string, unknown> | undefined,
|
|
320
|
+
): Attributes {
|
|
321
|
+
if (options === undefined) {
|
|
322
|
+
return {};
|
|
323
|
+
}
|
|
324
|
+
const mapping: Record<string, string> = {
|
|
325
|
+
choice_count: "gen_ai.request.choice.count",
|
|
326
|
+
frequency_penalty: "gen_ai.request.frequency_penalty",
|
|
327
|
+
max_completion_tokens: "gen_ai.request.max_tokens",
|
|
328
|
+
max_output_tokens: "gen_ai.request.max_tokens",
|
|
329
|
+
max_tokens: "gen_ai.request.max_tokens",
|
|
330
|
+
n: "gen_ai.request.choice.count",
|
|
331
|
+
presence_penalty: "gen_ai.request.presence_penalty",
|
|
332
|
+
seed: "gen_ai.request.seed",
|
|
333
|
+
temperature: "gen_ai.request.temperature",
|
|
334
|
+
top_k: "gen_ai.request.top_k",
|
|
335
|
+
top_p: "gen_ai.request.top_p",
|
|
336
|
+
};
|
|
337
|
+
const attributes: Attributes = {};
|
|
338
|
+
for (const [optionName, attributeName] of Object.entries(mapping)) {
|
|
339
|
+
const value = toAttributeValue(options[optionName]);
|
|
340
|
+
if (value !== undefined) {
|
|
341
|
+
attributes[attributeName] = value;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return attributes;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function cleanAttributes(attributes: Attributes): Attributes {
|
|
348
|
+
const cleaned: Attributes = {};
|
|
349
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
350
|
+
const attrValue = toAttributeValue(value);
|
|
351
|
+
if (key.trim() !== "" && attrValue !== undefined) {
|
|
352
|
+
cleaned[key] = attrValue;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return cleaned;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function toAttributeValue(value: unknown): AttributeValue | undefined {
|
|
359
|
+
if (value === undefined || value === null) {
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
if (typeof value === "string") {
|
|
363
|
+
const cleaned = cleanString(value);
|
|
364
|
+
return cleaned === "" ? undefined : cleaned;
|
|
365
|
+
}
|
|
366
|
+
if (typeof value === "number") {
|
|
367
|
+
return Number.isFinite(value) ? value : undefined;
|
|
368
|
+
}
|
|
369
|
+
if (typeof value === "boolean") {
|
|
370
|
+
return value;
|
|
371
|
+
}
|
|
372
|
+
if (Array.isArray(value)) {
|
|
373
|
+
const strings = value.filter(
|
|
374
|
+
(item): item is string => typeof item === "string",
|
|
375
|
+
);
|
|
376
|
+
if (strings.length === value.length && strings.length > 0) {
|
|
377
|
+
return strings;
|
|
378
|
+
}
|
|
379
|
+
const numbers = value.filter(
|
|
380
|
+
(item): item is number =>
|
|
381
|
+
typeof item === "number" && Number.isFinite(item),
|
|
382
|
+
);
|
|
383
|
+
if (numbers.length === value.length && numbers.length > 0) {
|
|
384
|
+
return numbers;
|
|
385
|
+
}
|
|
386
|
+
const booleans = value.filter(
|
|
387
|
+
(item): item is boolean => typeof item === "boolean",
|
|
388
|
+
);
|
|
389
|
+
if (booleans.length === value.length && booleans.length > 0) {
|
|
390
|
+
return booleans;
|
|
391
|
+
}
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
return String(value);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function tokenCount(value: number | undefined): number | undefined {
|
|
398
|
+
if (value === undefined || !Number.isFinite(value) || value < 0) {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
return value;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function exceptionForSpan(error: unknown): Exception {
|
|
405
|
+
return error instanceof Error ? error : String(error);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function errorType(error: unknown): string {
|
|
409
|
+
if (error instanceof Error && cleanString(error.name) !== "") {
|
|
410
|
+
return error.name;
|
|
411
|
+
}
|
|
412
|
+
return "_OTHER";
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function errorMessage(error: unknown): string {
|
|
416
|
+
if (error instanceof Error) {
|
|
417
|
+
return error.message;
|
|
418
|
+
}
|
|
419
|
+
return String(error);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function spanName(operation: string, subject: string): string {
|
|
423
|
+
const cleaned = cleanString(subject);
|
|
424
|
+
return cleaned === "" ? operation : `${operation} ${cleaned}`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function cleanString(value: unknown): string {
|
|
428
|
+
return typeof value === "string" ? value.trim() : "";
|
|
429
|
+
}
|
package/src/workflow-manager.ts
CHANGED
|
@@ -78,11 +78,13 @@ export type WorkflowManagerPublishEventInput = MessageInitShape<
|
|
|
78
78
|
export class WorkflowManager {
|
|
79
79
|
private readonly client: Client<typeof WorkflowManagerHostService>;
|
|
80
80
|
private readonly invocationToken: string;
|
|
81
|
+
private readonly idempotencyKey: string;
|
|
81
82
|
|
|
82
83
|
constructor(request: Request);
|
|
83
84
|
constructor(invocationToken: string);
|
|
84
85
|
constructor(requestOrToken: Request | string) {
|
|
85
86
|
this.invocationToken = normalizeInvocationToken(requestOrToken);
|
|
87
|
+
this.idempotencyKey = normalizeIdempotencyKey(requestOrToken);
|
|
86
88
|
|
|
87
89
|
const target = process.env[ENV_WORKFLOW_MANAGER_SOCKET];
|
|
88
90
|
if (!target) {
|
|
@@ -107,6 +109,7 @@ export class WorkflowManager {
|
|
|
107
109
|
): Promise<ManagedWorkflowScheduleMessage> {
|
|
108
110
|
return await this.client.createSchedule({
|
|
109
111
|
...request,
|
|
112
|
+
idempotencyKey: request.idempotencyKey?.trim() || this.idempotencyKey,
|
|
110
113
|
invocationToken: this.invocationToken,
|
|
111
114
|
});
|
|
112
115
|
}
|
|
@@ -161,6 +164,7 @@ export class WorkflowManager {
|
|
|
161
164
|
): Promise<ManagedWorkflowEventTriggerMessage> {
|
|
162
165
|
return await this.client.createEventTrigger({
|
|
163
166
|
...request,
|
|
167
|
+
idempotencyKey: request.idempotencyKey?.trim() || this.idempotencyKey,
|
|
164
168
|
invocationToken: this.invocationToken,
|
|
165
169
|
});
|
|
166
170
|
}
|
|
@@ -232,6 +236,13 @@ function normalizeInvocationToken(requestOrToken: Request | string): string {
|
|
|
232
236
|
return trimmed;
|
|
233
237
|
}
|
|
234
238
|
|
|
239
|
+
function normalizeIdempotencyKey(requestOrToken: Request | string): string {
|
|
240
|
+
if (typeof requestOrToken === "string") {
|
|
241
|
+
return "";
|
|
242
|
+
}
|
|
243
|
+
return requestOrToken.idempotencyKey.trim();
|
|
244
|
+
}
|
|
245
|
+
|
|
235
246
|
function workflowManagerTransportOptions(rawTarget: string): {
|
|
236
247
|
baseUrl: string;
|
|
237
248
|
nodeOptions?: { path: string };
|
package/src/manifest-metadata.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
2
|
-
|
|
3
|
-
import YAML from "yaml";
|
|
4
|
-
|
|
5
|
-
export type HTTPSecuritySchemeType =
|
|
6
|
-
| "hmac"
|
|
7
|
-
| "apiKey"
|
|
8
|
-
| "http"
|
|
9
|
-
| "none";
|
|
10
|
-
|
|
11
|
-
export type HTTPIn = "header" | "query";
|
|
12
|
-
|
|
13
|
-
export type HTTPAuthScheme = "basic" | "bearer";
|
|
14
|
-
|
|
15
|
-
export interface HTTPSecretRef {
|
|
16
|
-
env?: string;
|
|
17
|
-
secret?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface HTTPSecurityScheme {
|
|
21
|
-
type?: HTTPSecuritySchemeType;
|
|
22
|
-
description?: string;
|
|
23
|
-
signatureHeader?: string;
|
|
24
|
-
signaturePrefix?: string;
|
|
25
|
-
payloadTemplate?: string;
|
|
26
|
-
timestampHeader?: string;
|
|
27
|
-
maxAgeSeconds?: number;
|
|
28
|
-
name?: string;
|
|
29
|
-
in?: HTTPIn;
|
|
30
|
-
scheme?: HTTPAuthScheme;
|
|
31
|
-
secret?: HTTPSecretRef;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface HTTPMediaType {}
|
|
35
|
-
|
|
36
|
-
export interface HTTPRequestBody {
|
|
37
|
-
required?: boolean;
|
|
38
|
-
content?: Record<string, HTTPMediaType>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface HTTPAck {
|
|
42
|
-
status?: number;
|
|
43
|
-
headers?: Record<string, string>;
|
|
44
|
-
body?: any;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface HTTPBinding {
|
|
48
|
-
path: string;
|
|
49
|
-
method: string;
|
|
50
|
-
credentialMode?: "none" | "user";
|
|
51
|
-
requestBody?: HTTPRequestBody;
|
|
52
|
-
security: string;
|
|
53
|
-
target: string;
|
|
54
|
-
ack?: HTTPAck;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface PluginManifestMetadata {
|
|
58
|
-
securitySchemes?: Record<string, HTTPSecurityScheme>;
|
|
59
|
-
http?: Record<string, HTTPBinding>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function hasPluginManifestMetadata(
|
|
63
|
-
metadata: PluginManifestMetadata | null | undefined,
|
|
64
|
-
): boolean {
|
|
65
|
-
return !!(
|
|
66
|
-
metadata &&
|
|
67
|
-
((metadata.securitySchemes &&
|
|
68
|
-
Object.keys(metadata.securitySchemes).length > 0) ||
|
|
69
|
-
(metadata.http && Object.keys(metadata.http).length > 0))
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function manifestMetadataToYaml(
|
|
74
|
-
metadata: PluginManifestMetadata | Record<string, unknown>,
|
|
75
|
-
): string {
|
|
76
|
-
return YAML.stringify(toManifestMetadataJsonObject(metadata));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function writeManifestMetadataYaml(
|
|
80
|
-
path: string,
|
|
81
|
-
metadata: PluginManifestMetadata | Record<string, unknown>,
|
|
82
|
-
): void {
|
|
83
|
-
writeFileSync(path, manifestMetadataToYaml(metadata), "utf8");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function toManifestMetadataJsonObject(
|
|
87
|
-
metadata: PluginManifestMetadata | Record<string, unknown>,
|
|
88
|
-
): Record<string, unknown> {
|
|
89
|
-
if (!("securitySchemes" in metadata) && !("http" in metadata)) {
|
|
90
|
-
return {
|
|
91
|
-
...metadata,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const typedMetadata = metadata as PluginManifestMetadata;
|
|
96
|
-
const output: Record<string, unknown> = {};
|
|
97
|
-
if (
|
|
98
|
-
typedMetadata.securitySchemes &&
|
|
99
|
-
Object.keys(typedMetadata.securitySchemes).length > 0
|
|
100
|
-
) {
|
|
101
|
-
output.securitySchemes = typedMetadata.securitySchemes;
|
|
102
|
-
}
|
|
103
|
-
if (typedMetadata.http && Object.keys(typedMetadata.http).length > 0) {
|
|
104
|
-
output.http = typedMetadata.http;
|
|
105
|
-
}
|
|
106
|
-
return output;
|
|
107
|
-
}
|