@thinkhive/sdk 2.0.1 → 3.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.
@@ -0,0 +1,141 @@
1
+ /**
2
+ * ThinkHive SDK v3.0 - OpenAI Instrumentation
3
+ *
4
+ * Auto-instrumentation for OpenAI API calls including:
5
+ * - Chat completions
6
+ * - Assistants API
7
+ * - Tool calls
8
+ */
9
+ import type { ConversationMessage, SpanData } from '../core/types';
10
+ /**
11
+ * Wrap an OpenAI chat completion call with tracing
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import OpenAI from 'openai';
16
+ * import { wrapChatCompletion } from '@thinkhive/sdk/instrumentation/openai';
17
+ *
18
+ * const openai = new OpenAI();
19
+ *
20
+ * const result = await wrapChatCompletion(
21
+ * () => openai.chat.completions.create({
22
+ * model: 'gpt-4',
23
+ * messages: [{ role: 'user', content: 'Hello' }],
24
+ * }),
25
+ * {
26
+ * model: 'gpt-4',
27
+ * messages: [{ role: 'user', content: 'Hello' }],
28
+ * }
29
+ * );
30
+ * ```
31
+ */
32
+ export declare function wrapChatCompletion<T>(fn: () => Promise<T>, options?: {
33
+ model?: string;
34
+ messages?: Array<{
35
+ role: string;
36
+ content: string;
37
+ }>;
38
+ temperature?: number;
39
+ maxTokens?: number;
40
+ }): Promise<T>;
41
+ /**
42
+ * Wrap an OpenAI Assistants API call with tracing
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * import OpenAI from 'openai';
47
+ * import { wrapAssistantRun } from '@thinkhive/sdk/instrumentation/openai';
48
+ *
49
+ * const openai = new OpenAI();
50
+ *
51
+ * const run = await wrapAssistantRun(
52
+ * () => openai.beta.threads.runs.create(threadId, {
53
+ * assistant_id: assistantId,
54
+ * }),
55
+ * { assistantId, threadId }
56
+ * );
57
+ * ```
58
+ */
59
+ export declare function wrapAssistantRun<T>(fn: () => Promise<T>, options: {
60
+ assistantId: string;
61
+ threadId: string;
62
+ model?: string;
63
+ }): Promise<T>;
64
+ /**
65
+ * Wrap an OpenAI tool call with tracing
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const result = await wrapToolCall(
70
+ * () => executeMyTool(args),
71
+ * {
72
+ * toolName: 'search_orders',
73
+ * toolCallId: 'call_abc123',
74
+ * arguments: { orderId: '12345' },
75
+ * }
76
+ * );
77
+ * ```
78
+ */
79
+ export declare function wrapToolCall<T>(fn: () => Promise<T>, options: {
80
+ toolName: string;
81
+ toolCallId?: string;
82
+ arguments?: Record<string, unknown>;
83
+ }): Promise<T>;
84
+ /**
85
+ * Extract conversation messages from OpenAI format
86
+ */
87
+ export declare function extractConversationMessages(openaiMessages: Array<{
88
+ role: string;
89
+ content: string | null;
90
+ tool_calls?: Array<{
91
+ id: string;
92
+ function: {
93
+ name: string;
94
+ arguments: string;
95
+ };
96
+ }>;
97
+ }>): ConversationMessage[];
98
+ /**
99
+ * Extract span data from OpenAI response
100
+ */
101
+ export declare function extractSpanData(response: {
102
+ model?: string;
103
+ usage?: {
104
+ prompt_tokens?: number;
105
+ completion_tokens?: number;
106
+ total_tokens?: number;
107
+ };
108
+ choices?: Array<{
109
+ message?: {
110
+ tool_calls?: Array<{
111
+ id: string;
112
+ function: {
113
+ name: string;
114
+ arguments: string;
115
+ };
116
+ }>;
117
+ };
118
+ finish_reason?: string;
119
+ }>;
120
+ }, durationMs: number): SpanData;
121
+ /**
122
+ * Auto-instrument OpenAI client
123
+ * Call this during SDK init to automatically trace all OpenAI calls
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * import OpenAI from 'openai';
128
+ * import { instrumentOpenAIClient } from '@thinkhive/sdk/instrumentation/openai';
129
+ *
130
+ * const openai = new OpenAI();
131
+ * instrumentOpenAIClient(openai);
132
+ *
133
+ * // All subsequent calls are automatically traced
134
+ * const response = await openai.chat.completions.create({ ... });
135
+ * ```
136
+ */
137
+ export declare function instrumentOpenAIClient(client: any): void;
138
+ /**
139
+ * Remove instrumentation from OpenAI client
140
+ */
141
+ export declare function uninstrumentOpenAIClient(client: any, originalCreate: any): void;
@@ -0,0 +1,279 @@
1
+ "use strict";
2
+ /**
3
+ * ThinkHive SDK v3.0 - OpenAI Instrumentation
4
+ *
5
+ * Auto-instrumentation for OpenAI API calls including:
6
+ * - Chat completions
7
+ * - Assistants API
8
+ * - Tool calls
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.wrapChatCompletion = wrapChatCompletion;
12
+ exports.wrapAssistantRun = wrapAssistantRun;
13
+ exports.wrapToolCall = wrapToolCall;
14
+ exports.extractConversationMessages = extractConversationMessages;
15
+ exports.extractSpanData = extractSpanData;
16
+ exports.instrumentOpenAIClient = instrumentOpenAIClient;
17
+ exports.uninstrumentOpenAIClient = uninstrumentOpenAIClient;
18
+ const api_1 = require("@opentelemetry/api");
19
+ const config_1 = require("../core/config");
20
+ // ============================================================================
21
+ // OPENAI TRACING
22
+ // ============================================================================
23
+ /**
24
+ * Wrap an OpenAI chat completion call with tracing
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import OpenAI from 'openai';
29
+ * import { wrapChatCompletion } from '@thinkhive/sdk/instrumentation/openai';
30
+ *
31
+ * const openai = new OpenAI();
32
+ *
33
+ * const result = await wrapChatCompletion(
34
+ * () => openai.chat.completions.create({
35
+ * model: 'gpt-4',
36
+ * messages: [{ role: 'user', content: 'Hello' }],
37
+ * }),
38
+ * {
39
+ * model: 'gpt-4',
40
+ * messages: [{ role: 'user', content: 'Hello' }],
41
+ * }
42
+ * );
43
+ * ```
44
+ */
45
+ async function wrapChatCompletion(fn, options = {}) {
46
+ const tracer = api_1.trace.getTracer('thinkhive', '3.0.0');
47
+ return tracer.startActiveSpan('openai.chat.completions', {
48
+ attributes: {
49
+ 'openinference.span.kind': 'LLM',
50
+ 'llm.provider': 'openai',
51
+ 'llm.model_name': options.model,
52
+ 'llm.temperature': options.temperature,
53
+ 'llm.max_tokens': options.maxTokens,
54
+ 'llm.input_messages': options.messages
55
+ ? JSON.stringify(options.messages).substring(0, 10000)
56
+ : undefined,
57
+ },
58
+ }, async (span) => {
59
+ const startTime = Date.now();
60
+ try {
61
+ const result = await fn();
62
+ // Extract response data
63
+ const response = result;
64
+ if (response?.choices?.[0]?.message) {
65
+ span.setAttribute('llm.output_messages', JSON.stringify(response.choices[0].message).substring(0, 10000));
66
+ }
67
+ if (response?.usage) {
68
+ span.setAttribute('llm.token_count.prompt', response.usage.prompt_tokens);
69
+ span.setAttribute('llm.token_count.completion', response.usage.completion_tokens);
70
+ span.setAttribute('llm.token_count.total', response.usage.total_tokens);
71
+ }
72
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
73
+ return result;
74
+ }
75
+ catch (error) {
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message });
78
+ span.recordException(error);
79
+ throw error;
80
+ }
81
+ finally {
82
+ span.setAttribute('duration_ms', Date.now() - startTime);
83
+ span.end();
84
+ }
85
+ });
86
+ }
87
+ /**
88
+ * Wrap an OpenAI Assistants API call with tracing
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * import OpenAI from 'openai';
93
+ * import { wrapAssistantRun } from '@thinkhive/sdk/instrumentation/openai';
94
+ *
95
+ * const openai = new OpenAI();
96
+ *
97
+ * const run = await wrapAssistantRun(
98
+ * () => openai.beta.threads.runs.create(threadId, {
99
+ * assistant_id: assistantId,
100
+ * }),
101
+ * { assistantId, threadId }
102
+ * );
103
+ * ```
104
+ */
105
+ async function wrapAssistantRun(fn, options) {
106
+ const tracer = api_1.trace.getTracer('thinkhive', '3.0.0');
107
+ return tracer.startActiveSpan('openai.assistants.run', {
108
+ attributes: {
109
+ 'openinference.span.kind': 'AGENT',
110
+ 'llm.provider': 'openai',
111
+ 'llm.model_name': options.model,
112
+ 'assistant.id': options.assistantId,
113
+ 'assistant.thread_id': options.threadId,
114
+ },
115
+ }, async (span) => {
116
+ const startTime = Date.now();
117
+ try {
118
+ const result = await fn();
119
+ const response = result;
120
+ if (response?.status) {
121
+ span.setAttribute('assistant.run_status', response.status);
122
+ }
123
+ if (response?.usage) {
124
+ span.setAttribute('llm.token_count.prompt', response.usage.prompt_tokens);
125
+ span.setAttribute('llm.token_count.completion', response.usage.completion_tokens);
126
+ }
127
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
128
+ return result;
129
+ }
130
+ catch (error) {
131
+ const message = error instanceof Error ? error.message : String(error);
132
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message });
133
+ span.recordException(error);
134
+ throw error;
135
+ }
136
+ finally {
137
+ span.setAttribute('duration_ms', Date.now() - startTime);
138
+ span.end();
139
+ }
140
+ });
141
+ }
142
+ /**
143
+ * Wrap an OpenAI tool call with tracing
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const result = await wrapToolCall(
148
+ * () => executeMyTool(args),
149
+ * {
150
+ * toolName: 'search_orders',
151
+ * toolCallId: 'call_abc123',
152
+ * arguments: { orderId: '12345' },
153
+ * }
154
+ * );
155
+ * ```
156
+ */
157
+ async function wrapToolCall(fn, options) {
158
+ const tracer = api_1.trace.getTracer('thinkhive', '3.0.0');
159
+ return tracer.startActiveSpan(`tool.${options.toolName}`, {
160
+ attributes: {
161
+ 'openinference.span.kind': 'TOOL',
162
+ 'tool.name': options.toolName,
163
+ 'tool.call_id': options.toolCallId,
164
+ 'tool.parameters': options.arguments
165
+ ? JSON.stringify(options.arguments).substring(0, 10000)
166
+ : undefined,
167
+ },
168
+ }, async (span) => {
169
+ const startTime = Date.now();
170
+ try {
171
+ const result = await fn();
172
+ span.setAttribute('tool.output', JSON.stringify(result).substring(0, 10000));
173
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
174
+ return result;
175
+ }
176
+ catch (error) {
177
+ const message = error instanceof Error ? error.message : String(error);
178
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message });
179
+ span.recordException(error);
180
+ throw error;
181
+ }
182
+ finally {
183
+ span.setAttribute('duration_ms', Date.now() - startTime);
184
+ span.end();
185
+ }
186
+ });
187
+ }
188
+ // ============================================================================
189
+ // EXTRACTION HELPERS
190
+ // ============================================================================
191
+ /**
192
+ * Extract conversation messages from OpenAI format
193
+ */
194
+ function extractConversationMessages(openaiMessages) {
195
+ return openaiMessages.map((m) => ({
196
+ role: m.role,
197
+ content: m.content || '',
198
+ metadata: m.tool_calls
199
+ ? {
200
+ tool_calls: m.tool_calls.map((tc) => ({
201
+ id: tc.id,
202
+ name: tc.function.name,
203
+ arguments: tc.function.arguments,
204
+ })),
205
+ }
206
+ : undefined,
207
+ }));
208
+ }
209
+ /**
210
+ * Extract span data from OpenAI response
211
+ */
212
+ function extractSpanData(response, durationMs) {
213
+ const span = {
214
+ name: 'openai.chat.completions',
215
+ type: 'llm',
216
+ durationMs,
217
+ model: response.model,
218
+ provider: 'openai',
219
+ promptTokens: response.usage?.prompt_tokens,
220
+ completionTokens: response.usage?.completion_tokens,
221
+ status: 'ok',
222
+ };
223
+ // Extract tool calls as children
224
+ const toolCalls = response.choices?.[0]?.message?.tool_calls;
225
+ if (toolCalls && toolCalls.length > 0) {
226
+ span.children = toolCalls.map((tc) => ({
227
+ name: tc.function.name,
228
+ type: 'tool',
229
+ toolName: tc.function.name,
230
+ toolParameters: JSON.parse(tc.function.arguments),
231
+ }));
232
+ }
233
+ return span;
234
+ }
235
+ // ============================================================================
236
+ // AUTO-INSTRUMENTATION
237
+ // ============================================================================
238
+ /**
239
+ * Auto-instrument OpenAI client
240
+ * Call this during SDK init to automatically trace all OpenAI calls
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * import OpenAI from 'openai';
245
+ * import { instrumentOpenAIClient } from '@thinkhive/sdk/instrumentation/openai';
246
+ *
247
+ * const openai = new OpenAI();
248
+ * instrumentOpenAIClient(openai);
249
+ *
250
+ * // All subsequent calls are automatically traced
251
+ * const response = await openai.chat.completions.create({ ... });
252
+ * ```
253
+ */
254
+ function instrumentOpenAIClient(client) {
255
+ if (!client || !client.chat?.completions?.create) {
256
+ (0, config_1.debugLog)('OpenAI client not found or incompatible');
257
+ return;
258
+ }
259
+ const originalCreate = client.chat.completions.create.bind(client.chat.completions);
260
+ client.chat.completions.create = async function (params, options) {
261
+ return wrapChatCompletion(() => originalCreate(params, options), {
262
+ model: params.model,
263
+ messages: params.messages,
264
+ temperature: params.temperature,
265
+ maxTokens: params.max_tokens,
266
+ });
267
+ };
268
+ (0, config_1.debugLog)('OpenAI client instrumented');
269
+ }
270
+ /**
271
+ * Remove instrumentation from OpenAI client
272
+ */
273
+ function uninstrumentOpenAIClient(client, originalCreate) {
274
+ if (client?.chat?.completions) {
275
+ client.chat.completions.create = originalCreate;
276
+ (0, config_1.debugLog)('OpenAI client uninstrumented');
277
+ }
278
+ }
279
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2luc3RydW1lbnRhdGlvbi9vcGVuYWkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7O0dBT0c7O0FBZ0NILGdEQXlEQztBQW9CRCw0Q0FnREM7QUFpQkQsb0NBd0NDO0FBU0Qsa0VBdUJDO0FBS0QsMENBMkNDO0FBc0JELHdEQXFCQztBQUtELDREQUtDO0FBelZELDRDQUErRTtBQUMvRSwyQ0FBcUQ7QUFHckQsK0VBQStFO0FBQy9FLGlCQUFpQjtBQUNqQiwrRUFBK0U7QUFFL0U7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXFCRztBQUNJLEtBQUssVUFBVSxrQkFBa0IsQ0FDdEMsRUFBb0IsRUFDcEIsVUFLSSxFQUFFO0lBRU4sTUFBTSxNQUFNLEdBQUcsV0FBSyxDQUFDLFNBQVMsQ0FBQyxXQUFXLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFFckQsT0FBTyxNQUFNLENBQUMsZUFBZSxDQUMzQix5QkFBeUIsRUFDekI7UUFDRSxVQUFVLEVBQUU7WUFDVix5QkFBeUIsRUFBRSxLQUFLO1lBQ2hDLGNBQWMsRUFBRSxRQUFRO1lBQ3hCLGdCQUFnQixFQUFFLE9BQU8sQ0FBQyxLQUFLO1lBQy9CLGlCQUFpQixFQUFFLE9BQU8sQ0FBQyxXQUFXO1lBQ3RDLGdCQUFnQixFQUFFLE9BQU8sQ0FBQyxTQUFTO1lBQ25DLG9CQUFvQixFQUFFLE9BQU8sQ0FBQyxRQUFRO2dCQUNwQyxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUM7Z0JBQ3RELENBQUMsQ0FBQyxTQUFTO1NBQ2Q7S0FDRixFQUNELEtBQUssRUFBRSxJQUFJLEVBQUUsRUFBRTtRQUNiLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUM3QixJQUFJLENBQUM7WUFDSCxNQUFNLE1B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QUFrQixDQUN2QixHQUFHLEVBQUUsQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxFQUNyQztZQUNFLEtBQUssRUFBRSxNQUFNLENBQUMsS0FBSztZQUNuQixRQUFRLEVBQUUsTUFBTSxDQUFDLFFBQVE7WUFDekIsV0FBVyxFQUFFLE1BQU0sQ0FBQyxXQUFXO1lBQy9CLFNBQVMsRUFBRSxNQUFNLENBQUMsVUFBVTtTQUM3QixDQUNGLENBQUM7SUFDSixDQUFDLENBQUM7SUFFRixJQUFBLGlCQUFRLEVBQUMsNEJBQTRCLENBQUMsQ0FBQztBQUN6QyxDQUFDO0FBRUQ7O0dBRUc7QUFDSCxTQUFnQix3QkFBd0IsQ0FBQyxNQUFXLEVBQUUsY0FBbUI7SUFDdkUsSUFBSSxNQUFNLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxDQUFDO1FBQzlCLE1BQU0sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLE1BQU0sR0FBRyxjQUFjLENBQUM7UUFDaEQsSUFBQSxpQkFBUSxFQUFDLDhCQUE4QixDQUFDLENBQUM7SUFDM0MsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRoaW5rSGl2ZSBTREsgdjMuMCAtIE9wZW5BSSBJbnN0cnVtZW50YXRpb25cbiAqXG4gKiBBdXRvLWluc3RydW1lbnRhdGlvbiBmb3IgT3BlbkFJIEFQSSBjYWxscyBpbmNsdWRpbmc6XG4gKiAtIENoYXQgY29tcGxldGlvbnNcbiAqIC0gQXNzaXN0YW50cyBBUElcbiAqIC0gVG9vbCBjYWxsc1xuICovXG5cbmltcG9ydCB7IHRyYWNlLCBjb250ZXh0LCBTcGFuU3RhdHVzQ29kZSwgdHlwZSBTcGFuIH0gZnJvbSAnQG9wZW50ZWxlbWV0cnkvYXBpJztcbmltcG9ydCB7IGdldENvbmZpZywgZGVidWdMb2cgfSBmcm9tICcuLi9jb3JlL2NvbmZpZyc7XG5pbXBvcnQgdHlwZSB7IENvbnZlcnNhdGlvbk1lc3NhZ2UsIFNwYW5EYXRhIH0gZnJvbSAnLi4vY29yZS90eXBlcyc7XG5cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbi8vIE9QRU5BSSBUUkFDSU5HXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbi8qKlxuICogV3JhcCBhbiBPcGVuQUkgY2hhdCBjb21wbGV0aW9uIGNhbGwgd2l0aCB0cmFjaW5nXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCBPcGVuQUkgZnJvbSAnb3BlbmFpJztcbiAqIGltcG9ydCB7IHdyYXBDaGF0Q29tcGxldGlvbiB9IGZyb20gJ0B0aGlua2hpdmUvc2RrL2luc3RydW1lbnRhdGlvbi9vcGVuYWknO1xuICpcbiAqIGNvbnN0IG9wZW5haSA9IG5ldyBPcGVuQUkoKTtcbiAqXG4gKiBjb25zdCByZXN1bHQgPSBhd2FpdCB3cmFwQ2hhdENvbXBsZXRpb24oXG4gKiAgICgpID0+IG9wZW5haS5jaGF0LmNvbXBsZXRpb25zLmNyZWF0ZSh7XG4gKiAgICAgbW9kZWw6ICdncHQtNCcsXG4gKiAgICAgbWVzc2FnZXM6IFt7IHJvbGU6ICd1c2VyJywgY29udGVudDogJ0hlbGxvJyB9XSxcbiAqICAgfSksXG4gKiAgIHtcbiAqICAgICBtb2RlbDogJ2dwdC00JyxcbiAqICAgICBtZXNzYWdlczogW3sgcm9sZTogJ3VzZXInLCBjb250ZW50OiAnSGVsbG8nIH1dLFxuICogICB9XG4gKiApO1xuICogYGBgXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3cmFwQ2hhdENvbXBsZXRpb248VD4oXG4gIGZuOiAoKSA9PiBQcm9taXNlPFQ+LFxuICBvcHRpb25zOiB7XG4gICAgbW9kZWw/OiBzdHJpbmc7XG4gICAgbWVzc2FnZXM/OiBBcnJheTx7IHJvbGU6IHN0cmluZzsgY29udGVudDogc3RyaW5nIH0+O1xuICAgIHRlbXBlcmF0dXJlPzogbnVtYmVyO1xuICAgIG1heFRva2Vucz86IG51bWJlcjtcbiAgfSA9IHt9XG4pOiBQcm9taXNlPFQ+IHtcbiAgY29uc3QgdHJhY2VyID0gdHJhY2UuZ2V0VHJhY2VyKCd0aGlua2hpdmUnLCAnMy4wLjAnKTtcblxuICByZXR1cm4gdHJhY2VyLnN0YXJ0QWN0aXZlU3BhbihcbiAgICAnb3BlbmFpLmNoYXQuY29tcGxldGlvbnMnLFxuICAgIHtcbiAgICAgIGF0dHJpYnV0ZXM6IHtcbiAgICAgICAgJ29wZW5pbmZlcmVuY2Uuc3Bhbi5raW5kJzogJ0xMTScsXG4gICAgICAgICdsbG0ucHJvdmlkZXInOiAnb3BlbmFpJyxcbiAgICAgICAgJ2xsbS5tb2RlbF9uYW1lJzogb3B0aW9ucy5tb2RlbCxcbiAgICAgICAgJ2xsbS50ZW1wZXJhdHVyZSc6IG9wdGlvbnMudGVtcGVyYXR1cmUsXG4gICAgICAgICdsbG0ubWF4X3Rva2Vucyc6IG9wdGlvbnMubWF4VG9rZW5zLFxuICAgICAgICAnbGxtLmlucHV0X21lc3NhZ2VzJzogb3B0aW9ucy5tZXNzYWdlc1xuICAgICAgICAgID8gSlNPTi5zdHJpbmdpZnkob3B0aW9ucy5tZXNzYWdlcykuc3Vic3RyaW5nKDAsIDEwMDAwKVxuICAgICAgICAgIDogdW5kZWZpbmVkLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGFzeW5jIChzcGFuKSA9PiB7XG4gICAgICBjb25zdCBzdGFydFRpbWUgPSBEYXRlLm5vdygpO1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZm4oKTtcblxuICAgICAgICAvLyBFeHRyYWN0IHJlc3BvbnNlIGRhdGFcbiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSByZXN1bHQgYXMgYW55O1xuICAgICAgICBpZiAocmVzcG9uc2U/LmNob2ljZXM/LlswXT8ubWVzc2FnZSkge1xuICAgICAgICAgIHNwYW4uc2V0QXR0cmlidXRlKFxuICAgICAgICAgICAgJ2xsbS5vdXRwdXRfbWVzc2FnZXMnLFxuICAgICAgICAgICAgSlNPTi5zdHJpbmdpZnkocmVzcG9uc2UuY2hvaWNlc1swXS5tZXNzYWdlKS5zdWJzdHJpbmcoMCwgMTAwMDApXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAocmVzcG9uc2U/LnVzYWdlKSB7XG4gICAgICAgICAgc3Bhbi5zZXRBdHRyaWJ1dGUoJ2xsbS50b2tlbl9jb3VudC5wcm9tcHQnLCByZXNwb25zZS51c2FnZS5wcm9tcHRfdG9rZW5zKTtcbiAgICAgICAgICBzcGFuLnNldEF0dHJpYnV0ZSgnbGxtLnRva2VuX2NvdW50LmNvbXBsZXRpb24nLCByZXNwb25zZS51c2FnZS5jb21wbGV0aW9uX3Rva2Vucyk7XG4gICAgICAgICAgc3Bhbi5zZXRBdHRyaWJ1dGUoJ2xsbS50b2tlbl9jb3VudC50b3RhbCcsIHJlc3BvbnNlLnVzYWdlLnRvdGFsX3Rva2Vucyk7XG4gICAgICAgIH1cblxuICAgICAgICBzcGFuLnNldFN0YXR1cyh7IGNvZGU6IFNwYW5TdGF0dXNDb2RlLk9LIH0pO1xuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICAgICAgY29uc3QgbWVzc2FnZSA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKTtcbiAgICAgICAgc3Bhbi5zZXRTdGF0dXMoeyBjb2RlOiBTcGFuU3RhdHVzQ29kZS5FUlJPUiwgbWVzc2FnZSB9KTtcbiAgICAgICAgc3Bhbi5yZWNvcmRFeGNlcHRpb24oZXJyb3IgYXMgRXJyb3IpO1xuICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIHNwYW4uc2V0QXR0cmlidXRlKCdkdXJhdGlvbl9tcycsIERhdGUubm93KCkgLSBzdGFydFRpbWUpO1xuICAgICAgICBzcGFuLmVuZCgpO1xuICAgICAgfVxuICAgIH1cbiAgKTtcbn1cblxuLyoqXG4gKiBXcmFwIGFuIE9wZW5BSSBBc3Npc3RhbnRzIEFQSSBjYWxsIHdpdGggdHJhY2luZ1xuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBpbXBvcnQgT3BlbkFJIGZyb20gJ29wZW5haSc7XG4gKiBpbXBvcnQgeyB3cmFwQXNzaXN0YW50UnVuIH0gZnJvbSAnQHRoaW5raGl2ZS9zZGsvaW5zdHJ1bWVudGF0aW9uL29wZW5haSc7XG4gKlxuICogY29uc3Qgb3BlbmFpID0gbmV3IE9wZW5BSSgpO1xuICpcbiAqIGNvbnN0IHJ1biA9IGF3YWl0IHdyYXBBc3Npc3RhbnRSdW4oXG4gKiAgICgpID0+IG9wZW5haS5iZXRhLnRocmVhZHMucnVucy5jcmVhdGUodGhyZWFkSWQsIHtcbiAqICAgICBhc3Npc3RhbnRfaWQ6IGFzc2lzdGFudElkLFxuICogICB9KSxcbiAqICAgeyBhc3Npc3RhbnRJZCwgdGhyZWFkSWQgfVxuICogKTtcbiAqIGBgYFxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gd3JhcEFzc2lzdGFudFJ1bjxUPihcbiAgZm46ICgpID0+IFByb21pc2U8VD4sXG4gIG9wdGlvbnM6IHtcbiAgICBhc3Npc3RhbnRJZDogc3RyaW5nO1xuICAgIHRocmVhZElkOiBzdHJpbmc7XG4gICAgbW9kZWw/OiBzdHJpbmc7XG4gIH1cbik6IFByb21pc2U8VD4ge1xuICBjb25zdCB0cmFjZXIgPSB0cmFjZS5nZXRUcmFjZXIoJ3RoaW5raGl2ZScsICczLjAuMCcpO1xuXG4gIHJldHVybiB0cmFjZXIuc3RhcnRBY3RpdmVTcGFuKFxuICAgICdvcGVuYWkuYXNzaXN0YW50cy5ydW4nLFxuICAgIHtcbiAgICAgIGF0dHJpYnV0ZXM6IHtcbiAgICAgICAgJ29wZW5pbmZlcmVuY2Uuc3Bhbi5raW5kJzogJ0FHRU5UJyxcbiAgICAgICAgJ2xsbS5wcm92aWRlcic6ICdvcGVuYWknLFxuICAgICAgICAnbGxtLm1vZGVsX25hbWUnOiBvcHRpb25zLm1vZGVsLFxuICAgICAgICAnYXNzaXN0YW50LmlkJzogb3B0aW9ucy5hc3Npc3RhbnRJZCxcbiAgICAgICAgJ2Fzc2lzdGFudC50aHJlYWRfaWQnOiBvcHRpb25zLnRocmVhZElkLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGFzeW5jIChzcGFuKSA9PiB7XG4gICAgICBjb25zdCBzdGFydFRpbWUgPSBEYXRlLm5vdygpO1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZm4oKTtcblxuICAgICAgICBjb25zdCByZXNwb25zZSA9IHJlc3VsdCBhcyBhbnk7XG4gICAgICAgIGlmIChyZXNwb25zZT8uc3RhdHVzKSB7XG4gICAgICAgICAgc3Bhbi5zZXRBdHRyaWJ1dGUoJ2Fzc2lzdGFudC5ydW5fc3RhdHVzJywgcmVzcG9uc2Uuc3RhdHVzKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAocmVzcG9uc2U/LnVzYWdlKSB7XG4gICAgICAgICAgc3Bhbi5zZXRBdHRyaWJ1dGUoJ2xsbS50b2tlbl9jb3VudC5wcm9tcHQnLCByZXNwb25zZS51c2FnZS5wcm9tcHRfdG9rZW5zKTtcbiAgICAgICAgICBzcGFuLnNldEF0dHJpYnV0ZSgnbGxtLnRva2VuX2NvdW50LmNvbXBsZXRpb24nLCByZXNwb25zZS51c2FnZS5jb21wbGV0aW9uX3Rva2Vucyk7XG4gICAgICAgIH1cblxuICAgICAgICBzcGFuLnNldFN0YXR1cyh7IGNvZGU6IFNwYW5TdGF0dXNDb2RlLk9LIH0pO1xuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICAgICAgY29uc3QgbWVzc2FnZSA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKTtcbiAgICAgICAgc3Bhbi5zZXRTdGF0dXMoeyBjb2RlOiBTcGFuU3RhdHVzQ29kZS5FUlJPUiwgbWVzc2FnZSB9KTtcbiAgICAgICAgc3Bhbi5yZWNvcmRFeGNlcHRpb24oZXJyb3IgYXMgRXJyb3IpO1xuICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIHNwYW4uc2V0QXR0cmlidXRlKCdkdXJhdGlvbl9tcycsIERhdGUubm93KCkgLSBzdGFydFRpbWUpO1xuICAgICAgICBzcGFuLmVuZCgpO1xuICAgICAgfVxuICAgIH1cbiAgKTtcbn1cblxuLyoqXG4gKiBXcmFwIGFuIE9wZW5BSSB0b29sIGNhbGwgd2l0aCB0cmFjaW5nXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHdyYXBUb29sQ2FsbChcbiAqICAgKCkgPT4gZXhlY3V0ZU15VG9vbChhcmdzKSxcbiAqICAge1xuICogICAgIHRvb2xOYW1lOiAnc2VhcmNoX29yZGVycycsXG4gKiAgICAgdG9vbENhbGxJZDogJ2NhbGxfYWJjMTIzJyxcbiAqICAgICBhcmd1bWVudHM6IHsgb3JkZXJJZDogJzEyMzQ1JyB9LFxuICogICB9XG4gKiApO1xuICogYGBgXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3cmFwVG9vbENhbGw8VD4oXG4gIGZuOiAoKSA9PiBQcm9taXNlPFQ+LFxuICBvcHRpb25zOiB7XG4gICAgdG9vbE5hbWU6IHN0cmluZztcbiAgICB0b29sQ2FsbElkPzogc3RyaW5nO1xuICAgIGFyZ3VtZW50cz86IFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICB9XG4pOiBQcm9taXNlPFQ+IHtcbiAgY29uc3QgdHJhY2VyID0gdHJhY2UuZ2V0VHJhY2VyKCd0aGlua2hpdmUnLCAnMy4wLjAnKTtcblxuICByZXR1cm4gdHJhY2VyLnN0YXJ0QWN0aXZlU3BhbihcbiAgICBgdG9vbC4ke29wdGlvbnMudG9vbE5hbWV9YCxcbiAgICB7XG4gICAgICBhdHRyaWJ1dGVzOiB7XG4gICAgICAgICdvcGVuaW5mZXJlbmNlLnNwYW4ua2luZCc6ICdUT09MJyxcbiAgICAgICAgJ3Rvb2wubmFtZSc6IG9wdGlvbnMudG9vbE5hbWUsXG4gICAgICAgICd0b29sLmNhbGxfaWQnOiBvcHRpb25zLnRvb2xDYWxsSWQsXG4gICAgICAgICd0b29sLnBhcmFtZXRlcnMnOiBvcHRpb25zLmFyZ3VtZW50c1xuICAgICAgICAgID8gSlNPTi5zdHJpbmdpZnkob3B0aW9ucy5hcmd1bWVudHMpLnN1YnN0cmluZygwLCAxMDAwMClcbiAgICAgICAgICA6IHVuZGVmaW5lZCxcbiAgICAgIH0sXG4gICAgfSxcbiAgICBhc3luYyAoc3BhbikgPT4ge1xuICAgICAgY29uc3Qgc3RhcnRUaW1lID0gRGF0ZS5ub3coKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGZuKCk7XG4gICAgICAgIHNwYW4uc2V0QXR0cmlidXRlKCd0b29sLm91dHB1dCcsIEpTT04uc3RyaW5naWZ5KHJlc3VsdCkuc3Vic3RyaW5nKDAsIDEwMDAwKSk7XG4gICAgICAgIHNwYW4uc2V0U3RhdHVzKHsgY29kZTogU3BhblN0YXR1c0NvZGUuT0sgfSk7XG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICB9IGNhdGNoIChlcnJvcjogdW5rbm93bikge1xuICAgICAgICBjb25zdCBtZXNzYWdlID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpO1xuICAgICAgICBzcGFuLnNldFN0YXR1cyh7IGNvZGU6IFNwYW5TdGF0dXNDb2RlLkVSUk9SLCBtZXNzYWdlIH0pO1xuICAgICAgICBzcGFuLnJlY29yZEV4Y2VwdGlvbihlcnJvciBhcyBFcnJvcik7XG4gICAgICAgIHRocm93IGVycm9yO1xuICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgc3Bhbi5zZXRBdHRyaWJ1dGUoJ2R1cmF0aW9uX21zJywgRGF0ZS5ub3coKSAtIHN0YXJ0VGltZSk7XG4gICAgICAgIHNwYW4uZW5kKCk7XG4gICAgICB9XG4gICAgfVxuICApO1xufVxuXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4vLyBFWFRSQUNUSU9OIEhFTFBFUlNcbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuLyoqXG4gKiBFeHRyYWN0IGNvbnZlcnNhdGlvbiBtZXNzYWdlcyBmcm9tIE9wZW5BSSBmb3JtYXRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGV4dHJhY3RDb252ZXJzYXRpb25NZXNzYWdlcyhcbiAgb3BlbmFpTWVzc2FnZXM6IEFycmF5PHtcbiAgICByb2xlOiBzdHJpbmc7XG4gICAgY29udGVudDogc3RyaW5nIHwgbnVsbDtcbiAgICB0b29sX2NhbGxzPzogQXJyYXk8e1xuICAgICAgaWQ6IHN0cmluZztcbiAgICAgIGZ1bmN0aW9uOiB7IG5hbWU6IHN0cmluZzsgYXJndW1lbnRzOiBzdHJpbmcgfTtcbiAgICB9PjtcbiAgfT5cbik6IENvbnZlcnNhdGlvbk1lc3NhZ2VbXSB7XG4gIHJldHVybiBvcGVuYWlNZXNzYWdlcy5tYXAoKG0pID0+ICh7XG4gICAgcm9sZTogbS5yb2xlIGFzIENvbnZlcnNhdGlvbk1lc3NhZ2VbJ3JvbGUnXSxcbiAgICBjb250ZW50OiBtLmNvbnRlbnQgfHwgJycsXG4gICAgbWV0YWRhdGE6IG0udG9vbF9jYWxsc1xuICAgICAgPyB7XG4gICAgICAgICAgdG9vbF9jYWxsczogbS50b29sX2NhbGxzLm1hcCgodGMpID0+ICh7XG4gICAgICAgICAgICBpZDogdGMuaWQsXG4gICAgICAgICAgICBuYW1lOiB0Yy5mdW5jdGlvbi5uYW1lLFxuICAgICAgICAgICAgYXJndW1lbnRzOiB0Yy5mdW5jdGlvbi5hcmd1bWVudHMsXG4gICAgICAgICAgfSkpLFxuICAgICAgICB9XG4gICAgICA6IHVuZGVmaW5lZCxcbiAgfSkpO1xufVxuXG4vKipcbiAqIEV4dHJhY3Qgc3BhbiBkYXRhIGZyb20gT3BlbkFJIHJlc3BvbnNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBleHRyYWN0U3BhbkRhdGEoXG4gIHJlc3BvbnNlOiB7XG4gICAgbW9kZWw/OiBzdHJpbmc7XG4gICAgdXNhZ2U/OiB7XG4gICAgICBwcm9tcHRfdG9rZW5zPzogbnVtYmVyO1xuICAgICAgY29tcGxldGlvbl90b2tlbnM/OiBudW1iZXI7XG4gICAgICB0b3RhbF90b2tlbnM/OiBudW1iZXI7XG4gICAgfTtcbiAgICBjaG9pY2VzPzogQXJyYXk8e1xuICAgICAgbWVzc2FnZT86IHtcbiAgICAgICAgdG9vbF9jYWxscz86IEFycmF5PHtcbiAgICAgICAgICBpZDogc3RyaW5nO1xuICAgICAgICAgIGZ1bmN0aW9uOiB7IG5hbWU6IHN0cmluZzsgYXJndW1lbnRzOiBzdHJpbmcgfTtcbiAgICAgICAgfT47XG4gICAgICB9O1xuICAgICAgZmluaXNoX3JlYXNvbj86IHN0cmluZztcbiAgICB9PjtcbiAgfSxcbiAgZHVyYXRpb25NczogbnVtYmVyXG4pOiBTcGFuRGF0YSB7XG4gIGNvbnN0IHNwYW46IFNwYW5EYXRhID0ge1xuICAgIG5hbWU6ICdvcGVuYWkuY2hhdC5jb21wbGV0aW9ucycsXG4gICAgdHlwZTogJ2xsbScsXG4gICAgZHVyYXRpb25NcyxcbiAgICBtb2RlbDogcmVzcG9uc2UubW9kZWwsXG4gICAgcHJvdmlkZXI6ICdvcGVuYWknLFxuICAgIHByb21wdFRva2VuczogcmVzcG9uc2UudXNhZ2U/LnByb21wdF90b2tlbnMsXG4gICAgY29tcGxldGlvblRva2VuczogcmVzcG9uc2UudXNhZ2U/LmNvbXBsZXRpb25fdG9rZW5zLFxuICAgIHN0YXR1czogJ29rJyxcbiAgfTtcblxuICAvLyBFeHRyYWN0IHRvb2wgY2FsbHMgYXMgY2hpbGRyZW5cbiAgY29uc3QgdG9vbENhbGxzID0gcmVzcG9uc2UuY2hvaWNlcz8uWzBdPy5tZXNzYWdlPy50b29sX2NhbGxzO1xuICBpZiAodG9vbENhbGxzICYmIHRvb2xDYWxscy5sZW5ndGggPiAwKSB7XG4gICAgc3Bhbi5jaGlsZHJlbiA9IHRvb2xDYWxscy5tYXAoKHRjKSA9PiAoe1xuICAgICAgbmFtZTogdGMuZnVuY3Rpb24ubmFtZSxcbiAgICAgIHR5cGU6ICd0b29sJyBhcyBjb25zdCxcbiAgICAgIHRvb2xOYW1lOiB0Yy5mdW5jdGlvbi5uYW1lLFxuICAgICAgdG9vbFBhcmFtZXRlcnM6IEpTT04ucGFyc2UodGMuZnVuY3Rpb24uYXJndW1lbnRzKSxcbiAgICB9KSk7XG4gIH1cblxuICByZXR1cm4gc3Bhbjtcbn1cblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gQVVUTy1JTlNUUlVNRU5UQVRJT05cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuLyoqXG4gKiBBdXRvLWluc3RydW1lbnQgT3BlbkFJIGNsaWVudFxuICogQ2FsbCB0aGlzIGR1cmluZyBTREsgaW5pdCB0byBhdXRvbWF0aWNhbGx5IHRyYWNlIGFsbCBPcGVuQUkgY2FsbHNcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogaW1wb3J0IE9wZW5BSSBmcm9tICdvcGVuYWknO1xuICogaW1wb3J0IHsgaW5zdHJ1bWVudE9wZW5BSUNsaWVudCB9IGZyb20gJ0B0aGlua2hpdmUvc2RrL2luc3RydW1lbnRhdGlvbi9vcGVuYWknO1xuICpcbiAqIGNvbnN0IG9wZW5haSA9IG5ldyBPcGVuQUkoKTtcbiAqIGluc3RydW1lbnRPcGVuQUlDbGllbnQob3BlbmFpKTtcbiAqXG4gKiAvLyBBbGwgc3Vic2VxdWVudCBjYWxscyBhcmUgYXV0b21hdGljYWxseSB0cmFjZWRcbiAqIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgb3BlbmFpLmNoYXQuY29tcGxldGlvbnMuY3JlYXRlKHsgLi4uIH0pO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbnN0cnVtZW50T3BlbkFJQ2xpZW50KGNsaWVudDogYW55KTogdm9pZCB7XG4gIGlmICghY2xpZW50IHx8ICFjbGllbnQuY2hhdD8uY29tcGxldGlvbnM/LmNyZWF0ZSkge1xuICAgIGRlYnVnTG9nKCdPcGVuQUkgY2xpZW50IG5vdCBmb3VuZCBvciBpbmNvbXBhdGlibGUnKTtcbiAgICByZXR1cm47XG4gIH1cblxuICBjb25zdCBvcmlnaW5hbENyZWF0ZSA9IGNsaWVudC5jaGF0LmNvbXBsZXRpb25zLmNyZWF0ZS5iaW5kKGNsaWVudC5jaGF0LmNvbXBsZXRpb25zKTtcblxuICBjbGllbnQuY2hhdC5jb21wbGV0aW9ucy5jcmVhdGUgPSBhc3luYyBmdW5jdGlvbiAocGFyYW1zOiBhbnksIG9wdGlvbnM/OiBhbnkpIHtcbiAgICByZXR1cm4gd3JhcENoYXRDb21wbGV0aW9uKFxuICAgICAgKCkgPT4gb3JpZ2luYWxDcmVhdGUocGFyYW1zLCBvcHRpb25zKSxcbiAgICAgIHtcbiAgICAgICAgbW9kZWw6IHBhcmFtcy5tb2RlbCxcbiAgICAgICAgbWVzc2FnZXM6IHBhcmFtcy5tZXNzYWdlcyxcbiAgICAgICAgdGVtcGVyYXR1cmU6IHBhcmFtcy50ZW1wZXJhdHVyZSxcbiAgICAgICAgbWF4VG9rZW5zOiBwYXJhbXMubWF4X3Rva2VucyxcbiAgICAgIH1cbiAgICApO1xuICB9O1xuXG4gIGRlYnVnTG9nKCdPcGVuQUkgY2xpZW50IGluc3RydW1lbnRlZCcpO1xufVxuXG4vKipcbiAqIFJlbW92ZSBpbnN0cnVtZW50YXRpb24gZnJvbSBPcGVuQUkgY2xpZW50XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB1bmluc3RydW1lbnRPcGVuQUlDbGllbnQoY2xpZW50OiBhbnksIG9yaWdpbmFsQ3JlYXRlOiBhbnkpOiB2b2lkIHtcbiAgaWYgKGNsaWVudD8uY2hhdD8uY29tcGxldGlvbnMpIHtcbiAgICBjbGllbnQuY2hhdC5jb21wbGV0aW9ucy5jcmVhdGUgPSBvcmlnaW5hbENyZWF0ZTtcbiAgICBkZWJ1Z0xvZygnT3BlbkFJIGNsaWVudCB1bmluc3RydW1lbnRlZCcpO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,203 @@
1
+ /**
2
+ * ThinkHive SDK v3.0 - Customer Context
3
+ *
4
+ * Time-series customer metrics snapshots
5
+ * Captures ARR, health score, segment AS OF the run time (not current values)
6
+ */
7
+ import type { CustomerContextSnapshot } from '../core/types';
8
+ export interface CustomerAccount {
9
+ id: string;
10
+ companyId: string;
11
+ externalId?: string;
12
+ externalSource?: 'salesforce' | 'hubspot' | 'zendesk' | 'intercom' | 'custom';
13
+ name: string;
14
+ domain?: string;
15
+ segment?: string;
16
+ industry?: string;
17
+ employeeCount?: number;
18
+ createdAt: string;
19
+ updatedAt: string;
20
+ }
21
+ export interface CustomerMetricsSnapshot {
22
+ id: string;
23
+ customerAccountId: string;
24
+ arr?: number;
25
+ healthScore?: number;
26
+ nps?: number;
27
+ segment?: string;
28
+ churnRisk?: 'low' | 'medium' | 'high';
29
+ capturedAt: string;
30
+ source?: string;
31
+ createdAt: string;
32
+ }
33
+ /**
34
+ * Customer context API client for time-series metrics
35
+ */
36
+ export declare const customerContext: {
37
+ /**
38
+ * Create a customer account
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const account = await customerContext.createAccount({
43
+ * name: 'Acme Corp',
44
+ * externalId: 'sf_001234',
45
+ * externalSource: 'salesforce',
46
+ * segment: 'enterprise',
47
+ * });
48
+ * ```
49
+ */
50
+ createAccount(input: {
51
+ name: string;
52
+ externalId?: string;
53
+ externalSource?: CustomerAccount["externalSource"];
54
+ domain?: string;
55
+ segment?: string;
56
+ industry?: string;
57
+ employeeCount?: number;
58
+ }): Promise<CustomerAccount>;
59
+ /**
60
+ * Get a customer account by ID
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const account = await customerContext.getAccount('cust_abc123');
65
+ * ```
66
+ */
67
+ getAccount(customerId: string): Promise<CustomerAccount>;
68
+ /**
69
+ * Get a customer account by external ID
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const account = await customerContext.getAccountByExternalId(
74
+ * 'sf_001234',
75
+ * 'salesforce'
76
+ * );
77
+ * ```
78
+ */
79
+ getAccountByExternalId(externalId: string, source: CustomerAccount["externalSource"]): Promise<CustomerAccount | null>;
80
+ /**
81
+ * Capture a metrics snapshot for a customer
82
+ * This creates a point-in-time record of the customer's metrics
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * // Capture current metrics
87
+ * const snapshot = await customerContext.captureSnapshot('cust_abc123', {
88
+ * arr: 120000,
89
+ * healthScore: 85,
90
+ * nps: 45,
91
+ * segment: 'enterprise',
92
+ * });
93
+ *
94
+ * // Use this snapshot in a run
95
+ * const run = await runs.create({
96
+ * agentId: 'agent_123',
97
+ * customerContext: {
98
+ * customerId: 'cust_abc123',
99
+ * arr: snapshot.arr,
100
+ * healthScore: snapshot.healthScore,
101
+ * capturedAt: snapshot.capturedAt,
102
+ * },
103
+ * // ...
104
+ * });
105
+ * ```
106
+ */
107
+ captureSnapshot(customerId: string, metrics: {
108
+ arr?: number;
109
+ healthScore?: number;
110
+ nps?: number;
111
+ segment?: string;
112
+ churnRisk?: "low" | "medium" | "high";
113
+ source?: string;
114
+ }): Promise<CustomerMetricsSnapshot>;
115
+ /**
116
+ * Get metrics snapshots for a customer (time-series)
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * // Get last 30 days of snapshots
121
+ * const snapshots = await customerContext.getSnapshots('cust_abc123', {
122
+ * from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
123
+ * });
124
+ * ```
125
+ */
126
+ getSnapshots(customerId: string, options?: {
127
+ from?: string;
128
+ to?: string;
129
+ limit?: number;
130
+ }): Promise<CustomerMetricsSnapshot[]>;
131
+ /**
132
+ * Get the most recent snapshot for a customer
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * const latest = await customerContext.getLatestSnapshot('cust_abc123');
137
+ * ```
138
+ */
139
+ getLatestSnapshot(customerId: string): Promise<CustomerMetricsSnapshot | null>;
140
+ /**
141
+ * Get snapshot closest to a specific timestamp
142
+ * Useful for retroactive analysis
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * // Get metrics as of a specific date
147
+ * const snapshot = await customerContext.getSnapshotAsOf(
148
+ * 'cust_abc123',
149
+ * '2024-01-15T10:00:00Z'
150
+ * );
151
+ * ```
152
+ */
153
+ getSnapshotAsOf(customerId: string, timestamp: string | Date): Promise<CustomerMetricsSnapshot | null>;
154
+ };
155
+ /**
156
+ * Create a CustomerContextSnapshot from a metrics snapshot
157
+ */
158
+ export declare function toContextSnapshot(snapshot: CustomerMetricsSnapshot): CustomerContextSnapshot;
159
+ /**
160
+ * Capture metrics and create context snapshot in one call
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * const context = await captureCustomerContext('cust_abc123', {
165
+ * arr: 100000,
166
+ * healthScore: 90,
167
+ * segment: 'enterprise',
168
+ * });
169
+ *
170
+ * // Use in run
171
+ * const run = await runs.create({
172
+ * agentId: 'agent_123',
173
+ * customerContext: context,
174
+ * // ...
175
+ * });
176
+ * ```
177
+ */
178
+ export declare function captureCustomerContext(customerId: string, metrics: {
179
+ arr?: number;
180
+ healthScore?: number;
181
+ segment?: string;
182
+ }): Promise<CustomerContextSnapshot>;
183
+ /**
184
+ * Get customer context as of a specific time
185
+ */
186
+ export declare function getContextAsOf(customerId: string, timestamp: string | Date): Promise<CustomerContextSnapshot | null>;
187
+ /**
188
+ * Calculate ARR change between two snapshots
189
+ */
190
+ export declare function calculateArrChange(older: CustomerMetricsSnapshot, newer: CustomerMetricsSnapshot): {
191
+ absolute: number;
192
+ percentage: number;
193
+ direction: 'increase' | 'decrease' | 'stable';
194
+ };
195
+ /**
196
+ * Calculate health score trend
197
+ */
198
+ export declare function calculateHealthTrend(snapshots: CustomerMetricsSnapshot[]): {
199
+ currentScore: number | null;
200
+ avgScore: number | null;
201
+ trend: 'improving' | 'declining' | 'stable';
202
+ volatility: 'low' | 'medium' | 'high';
203
+ };