@qloo/qloo-harness 0.1.18

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,2195 @@
1
+ import { createRequire as __qlooCreateRequire } from "node:module";
2
+ const require = __qlooCreateRequire(import.meta.url);
3
+
4
+ // packages/qloo-client-ts/dist/config.js
5
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { dirname, isAbsolute, join, parse, resolve } from "node:path";
8
+ var QLOO_PRODUCTION_BASE_URL = "https://api.qloo.com";
9
+ function nonEmptyString(value) {
10
+ if (typeof value !== "string")
11
+ return void 0;
12
+ const trimmed = value.trim();
13
+ return trimmed || void 0;
14
+ }
15
+ function selectFileConfig(value) {
16
+ if (value === null || typeof value !== "object" || Array.isArray(value))
17
+ return {};
18
+ const record = value;
19
+ const apiKey = nonEmptyString(record.api_key);
20
+ const baseUrl = nonEmptyString(record.base_url);
21
+ const trustedBaseUrl = nonEmptyString(record.trusted_base_url);
22
+ const tasteResolverUrl = nonEmptyString(record.taste_resolver_url);
23
+ return {
24
+ ...apiKey ? { api_key: apiKey } : {},
25
+ ...baseUrl ? { base_url: baseUrl } : {},
26
+ ...trustedBaseUrl ? { trusted_base_url: trustedBaseUrl } : {},
27
+ ...tasteResolverUrl ? { taste_resolver_url: tasteResolverUrl } : {}
28
+ };
29
+ }
30
+ function comparableBaseUrl(value) {
31
+ try {
32
+ const url = new URL(value);
33
+ if (url.username || url.password || url.search || url.hash)
34
+ return void 0;
35
+ url.pathname = url.pathname.replace(/\/+$/u, "") || "/";
36
+ return url.toString();
37
+ } catch {
38
+ return void 0;
39
+ }
40
+ }
41
+ function areEquivalentQlooBaseUrls(left, right) {
42
+ const normalizedLeft = comparableBaseUrl(left);
43
+ return normalizedLeft !== void 0 && normalizedLeft === comparableBaseUrl(right);
44
+ }
45
+ function isOfficialQlooBaseUrl(value) {
46
+ return areEquivalentQlooBaseUrls(value, QLOO_PRODUCTION_BASE_URL);
47
+ }
48
+ function parseQlooConfig(content) {
49
+ const trimmed = content.trim();
50
+ if (!trimmed)
51
+ return {};
52
+ if (trimmed.startsWith("{"))
53
+ return selectFileConfig(JSON.parse(trimmed));
54
+ const values = {};
55
+ for (const line of content.split(/\r?\n/u)) {
56
+ const candidate = line.trim();
57
+ if (!candidate || candidate.startsWith("#"))
58
+ continue;
59
+ const separator = candidate.indexOf("=");
60
+ if (separator < 0)
61
+ continue;
62
+ const key = candidate.slice(0, separator).trim();
63
+ let value = candidate.slice(separator + 1).trim();
64
+ if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
65
+ value = value.slice(1, -1);
66
+ }
67
+ values[key] = value;
68
+ }
69
+ return selectFileConfig(values);
70
+ }
71
+ function loadQlooConfig(configPath, readFile) {
72
+ try {
73
+ const content = readFile ? readFile(configPath) : readFileSync(configPath, "utf8");
74
+ return parseQlooConfig(content);
75
+ } catch {
76
+ return {};
77
+ }
78
+ }
79
+ function resolveQlooConfigPath(options = {}) {
80
+ const env = options.env ?? process.env;
81
+ const homeDirectory = resolve(options.homeDirectory ?? homedir());
82
+ const configuredRoot = nonEmptyString(env.QLOO_HOME);
83
+ if (!configuredRoot)
84
+ return join(homeDirectory, ".qloo", "config");
85
+ const root = configuredRoot === "~" ? homeDirectory : configuredRoot.startsWith("~/") || configuredRoot.startsWith("~\\") ? join(homeDirectory, configuredRoot.slice(2)) : isAbsolute(configuredRoot) ? resolve(configuredRoot) : resolve(configuredRoot);
86
+ if (root === homeDirectory || root === parse(root).root) {
87
+ throw new Error("QLOO_HOME must name a dedicated subdirectory, not a home or filesystem root.");
88
+ }
89
+ return join(root, "config");
90
+ }
91
+ function resolveQlooClientConfiguration(options = {}) {
92
+ const env = options.env ?? process.env;
93
+ const configPath = options.configPath ?? resolveQlooConfigPath({ env });
94
+ const fileConfig = loadQlooConfig(configPath, options.readFile);
95
+ const environmentApiKey = nonEmptyString(env.QLOO_API_KEY);
96
+ const environmentBaseUrl = nonEmptyString(env.QLOO_BASE_URL);
97
+ const environmentTrustedBaseUrl = nonEmptyString(env.QLOO_TRUSTED_BASE_URL);
98
+ const apiKey = environmentApiKey ?? fileConfig.api_key;
99
+ const baseUrl = environmentBaseUrl ?? fileConfig.base_url;
100
+ const effectiveBaseUrl = baseUrl ?? QLOO_PRODUCTION_BASE_URL;
101
+ const baseUrlTrustSource = isOfficialQlooBaseUrl(effectiveBaseUrl) ? "official" : environmentTrustedBaseUrl && areEquivalentQlooBaseUrls(effectiveBaseUrl, environmentTrustedBaseUrl) ? "environment" : fileConfig.trusted_base_url && areEquivalentQlooBaseUrls(effectiveBaseUrl, fileConfig.trusted_base_url) ? "config" : "missing";
102
+ return {
103
+ ...apiKey ? { apiKey } : {},
104
+ apiKeySource: environmentApiKey ? "environment" : fileConfig.api_key ? "config" : "missing",
105
+ ...baseUrl ? { baseUrl } : {},
106
+ baseUrlSource: environmentBaseUrl ? "environment" : fileConfig.base_url ? "config" : "default",
107
+ baseUrlTrusted: baseUrlTrustSource !== "missing",
108
+ baseUrlTrustSource,
109
+ fileConfig
110
+ };
111
+ }
112
+
113
+ // packages/qloo-client-ts/dist/errors.js
114
+ var QlooClientError = class extends Error {
115
+ code;
116
+ retryable;
117
+ status;
118
+ requestId;
119
+ details;
120
+ constructor(code, message, options = {}) {
121
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
122
+ this.name = "QlooClientError";
123
+ this.code = code;
124
+ this.retryable = options.retryable ?? false;
125
+ if (options.status !== void 0)
126
+ this.status = options.status;
127
+ if (options.requestId !== void 0)
128
+ this.requestId = options.requestId;
129
+ if (options.details !== void 0)
130
+ this.details = options.details;
131
+ }
132
+ toJSON() {
133
+ return {
134
+ name: this.name,
135
+ code: this.code,
136
+ message: this.message,
137
+ retryable: this.retryable,
138
+ ...this.status === void 0 ? {} : { status: this.status },
139
+ ...this.requestId === void 0 ? {} : { requestId: this.requestId },
140
+ ...this.details === void 0 ? {} : { details: this.details }
141
+ };
142
+ }
143
+ };
144
+ var DNS_CODES = /* @__PURE__ */ new Set([
145
+ "EAI_AGAIN",
146
+ "EAI_FAIL",
147
+ "ENODATA",
148
+ "ENOTFOUND",
149
+ "ENOTIMP",
150
+ "ENOTINITIALIZED",
151
+ "ESERVFAIL"
152
+ ]);
153
+ var CONNECTION_CODES = /* @__PURE__ */ new Set([
154
+ "ECONNABORTED",
155
+ "ECONNREFUSED",
156
+ "ECONNRESET",
157
+ "EHOSTDOWN",
158
+ "EHOSTUNREACH",
159
+ "ENETDOWN",
160
+ "ENETUNREACH",
161
+ "EPIPE",
162
+ "ETIMEDOUT",
163
+ "UND_ERR_CONNECT_TIMEOUT",
164
+ "UND_ERR_SOCKET"
165
+ ]);
166
+ var TLS_CODES = /* @__PURE__ */ new Set([
167
+ "CERT_HAS_EXPIRED",
168
+ "CERT_NOT_YET_VALID",
169
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
170
+ "ERR_TLS_CERT_ALTNAME_INVALID",
171
+ "SELF_SIGNED_CERT_IN_CHAIN",
172
+ "UNABLE_TO_GET_ISSUER_CERT",
173
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
174
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE"
175
+ ]);
176
+ var PROXY_CODES = /* @__PURE__ */ new Set([
177
+ "ERR_PROXY_AUTH_UNSUPPORTED",
178
+ "UND_ERR_PRX"
179
+ ]);
180
+ function isRecord(value) {
181
+ return typeof value === "object" && value !== null;
182
+ }
183
+ function safeErrorCode(value) {
184
+ if (!isRecord(value))
185
+ return void 0;
186
+ const code = value.code;
187
+ return typeof code === "string" && /^[A-Z][A-Z0-9_]{1,63}$/u.test(code) ? code : void 0;
188
+ }
189
+ function errorChain(value) {
190
+ const chain = [];
191
+ const seen = /* @__PURE__ */ new Set();
192
+ let current = value;
193
+ for (let depth = 0; depth < 8 && current !== void 0 && !seen.has(current); depth += 1) {
194
+ chain.push(current);
195
+ seen.add(current);
196
+ if (!isRecord(current))
197
+ break;
198
+ if (Array.isArray(current.errors) && current.errors.length > 0) {
199
+ chain.push(...current.errors.slice(0, 4));
200
+ }
201
+ current = current.cause;
202
+ }
203
+ return chain;
204
+ }
205
+ function classifyQlooNetworkFailure(cause) {
206
+ const chain = errorChain(cause);
207
+ const systemCode = chain.map(safeErrorCode).find((value) => value !== void 0);
208
+ const names = chain.map((value) => isRecord(value) && typeof value.name === "string" ? value.name : void 0).filter((value) => value !== void 0);
209
+ if (names.includes("AbortError") || systemCode === "ABORT_ERR") {
210
+ return {
211
+ code: "QLOO_ABORTED",
212
+ kind: "aborted",
213
+ message: "Qloo API request was cancelled.",
214
+ retryable: false,
215
+ ...systemCode ? { systemCode } : {}
216
+ };
217
+ }
218
+ if (systemCode && DNS_CODES.has(systemCode)) {
219
+ return {
220
+ code: "QLOO_DNS_ERROR",
221
+ kind: "dns",
222
+ message: "Could not resolve the Qloo API hostname. Check DNS and VPN configuration.",
223
+ retryable: true,
224
+ systemCode
225
+ };
226
+ }
227
+ if (systemCode && (TLS_CODES.has(systemCode) || systemCode.startsWith("ERR_TLS_") || systemCode.startsWith("ERR_SSL_"))) {
228
+ return {
229
+ code: "QLOO_TLS_ERROR",
230
+ kind: "tls",
231
+ message: "Could not establish a trusted TLS connection to the Qloo API.",
232
+ retryable: false,
233
+ systemCode
234
+ };
235
+ }
236
+ if (systemCode && PROXY_CODES.has(systemCode)) {
237
+ return {
238
+ code: "QLOO_PROXY_ERROR",
239
+ kind: "proxy",
240
+ message: "The configured network proxy could not reach the Qloo API.",
241
+ retryable: true,
242
+ systemCode
243
+ };
244
+ }
245
+ if (systemCode && CONNECTION_CODES.has(systemCode)) {
246
+ return {
247
+ code: "QLOO_CONNECTION_ERROR",
248
+ kind: "connection",
249
+ message: "Could not open a connection to the Qloo API. Check VPN, routing, and firewall configuration.",
250
+ retryable: true,
251
+ systemCode
252
+ };
253
+ }
254
+ return {
255
+ code: "QLOO_NETWORK_ERROR",
256
+ kind: "network",
257
+ message: "Unable to reach the Qloo API because of an unclassified network failure.",
258
+ retryable: true,
259
+ ...systemCode ? { systemCode } : {}
260
+ };
261
+ }
262
+
263
+ // packages/qloo-client-ts/dist/client.js
264
+ var DEFAULT_TIMEOUT_MS = 2e4;
265
+ var DEFAULT_MAX_ATTEMPTS = 3;
266
+ var DEFAULT_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
267
+ var DEFAULT_MAX_RETRY_DELAY_MS = 3e4;
268
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
269
+ function validatePositiveInteger(name, value) {
270
+ if (!Number.isSafeInteger(value) || value < 1) {
271
+ throw new QlooClientError("QLOO_CONFIGURATION_ERROR", `${name} must be a positive integer.`);
272
+ }
273
+ }
274
+ function normalizeBaseUrl(value) {
275
+ let url;
276
+ try {
277
+ url = new URL(value);
278
+ } catch (cause) {
279
+ throw new QlooClientError("QLOO_CONFIGURATION_ERROR", "Qloo base URL is invalid.", {
280
+ cause
281
+ });
282
+ }
283
+ const isLoopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
284
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback)) {
285
+ throw new QlooClientError("QLOO_CONFIGURATION_ERROR", "Qloo base URL must use HTTPS (HTTP is allowed only for a loopback test server).");
286
+ }
287
+ if (url.username || url.password || url.search || url.hash) {
288
+ throw new QlooClientError("QLOO_CONFIGURATION_ERROR", "Qloo base URL cannot contain credentials, query parameters, or a fragment.");
289
+ }
290
+ url.pathname = url.pathname.replace(/\/+$/, "") + "/";
291
+ return url;
292
+ }
293
+ function appendQuery(url, query) {
294
+ for (const [key, value] of Object.entries(query)) {
295
+ if (value === void 0 || value === null || value === "")
296
+ continue;
297
+ const serialized = Array.isArray(value) ? value.join(",") : String(value);
298
+ if (serialized !== "")
299
+ url.searchParams.set(key, serialized);
300
+ }
301
+ }
302
+ function requestIdFrom(response) {
303
+ return response.headers.get("x-qloo-request-id") ?? response.headers.get("x-request-id") ?? response.headers.get("request-id") ?? void 0;
304
+ }
305
+ function responseErrorMetadata(response) {
306
+ const requestId = requestIdFrom(response);
307
+ return {
308
+ status: response.status,
309
+ ...requestId ? { requestId } : {}
310
+ };
311
+ }
312
+ function retryAfterMilliseconds(response, now = Date.now()) {
313
+ const value = response.headers.get("retry-after");
314
+ if (!value)
315
+ return void 0;
316
+ const seconds = Number(value);
317
+ if (Number.isFinite(seconds) && seconds >= 0)
318
+ return seconds * 1e3;
319
+ const date = Date.parse(value);
320
+ if (!Number.isNaN(date))
321
+ return Math.max(0, date - now);
322
+ return void 0;
323
+ }
324
+ function responseMessage(body, response) {
325
+ if (body && typeof body === "object") {
326
+ for (const key of ["message", "reason", "error"]) {
327
+ const value = Reflect.get(body, key);
328
+ if (typeof value === "string" && value.trim())
329
+ return value;
330
+ }
331
+ }
332
+ return `Qloo API request failed with ${response.status} ${response.statusText || "HTTP error"}.`;
333
+ }
334
+ function createAttemptSignal(parent, timeoutMs) {
335
+ const controller = new AbortController();
336
+ let timedOut = false;
337
+ const timeout = setTimeout(() => {
338
+ timedOut = true;
339
+ controller.abort(new Error("Qloo request timed out."));
340
+ }, timeoutMs);
341
+ timeout.unref?.();
342
+ const onParentAbort = () => controller.abort(parent?.reason);
343
+ if (parent?.aborted)
344
+ onParentAbort();
345
+ else
346
+ parent?.addEventListener("abort", onParentAbort, { once: true });
347
+ return {
348
+ signal: controller.signal,
349
+ didTimeout: () => timedOut,
350
+ dispose: () => {
351
+ clearTimeout(timeout);
352
+ parent?.removeEventListener("abort", onParentAbort);
353
+ }
354
+ };
355
+ }
356
+ async function defaultSleep(milliseconds, signal) {
357
+ if (signal?.aborted)
358
+ throw signal.reason;
359
+ await new Promise((resolve2, reject) => {
360
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
361
+ const timeout = setTimeout(() => {
362
+ cleanup();
363
+ resolve2();
364
+ }, milliseconds);
365
+ const onAbort = () => {
366
+ clearTimeout(timeout);
367
+ cleanup();
368
+ reject(signal?.reason);
369
+ };
370
+ signal?.addEventListener("abort", onAbort, { once: true });
371
+ });
372
+ }
373
+ async function readBoundedBody(response, maxBytes) {
374
+ const contentLength = response.headers.get("content-length");
375
+ if (contentLength !== null && Number(contentLength) > maxBytes) {
376
+ await response.body?.cancel();
377
+ throw new QlooClientError("QLOO_RESPONSE_TOO_LARGE", `Qloo API response exceeded the ${maxBytes}-byte limit.`, responseErrorMetadata(response));
378
+ }
379
+ if (!response.body)
380
+ return "";
381
+ const reader = response.body.getReader();
382
+ const chunks = [];
383
+ let total = 0;
384
+ while (true) {
385
+ const { done, value } = await reader.read();
386
+ if (done)
387
+ break;
388
+ if (!value)
389
+ continue;
390
+ total += value.byteLength;
391
+ if (total > maxBytes) {
392
+ await reader.cancel();
393
+ throw new QlooClientError("QLOO_RESPONSE_TOO_LARGE", `Qloo API response exceeded the ${maxBytes}-byte limit.`, responseErrorMetadata(response));
394
+ }
395
+ chunks.push(value);
396
+ }
397
+ const body = new Uint8Array(total);
398
+ let offset = 0;
399
+ for (const chunk of chunks) {
400
+ body.set(chunk, offset);
401
+ offset += chunk.byteLength;
402
+ }
403
+ return new TextDecoder().decode(body);
404
+ }
405
+ function parseJsonBody(text, response) {
406
+ if (!text.trim())
407
+ return {};
408
+ try {
409
+ return JSON.parse(text);
410
+ } catch (cause) {
411
+ throw new QlooClientError("QLOO_RESPONSE_ERROR", "Qloo API returned invalid JSON.", {
412
+ cause,
413
+ retryable: response.status >= 500,
414
+ ...responseErrorMetadata(response)
415
+ });
416
+ }
417
+ }
418
+ var QlooClient = class {
419
+ baseUrl;
420
+ #apiKey;
421
+ #baseUrl;
422
+ #timeoutMs;
423
+ #maxAttempts;
424
+ #maxResponseBytes;
425
+ #maxRetryDelayMs;
426
+ #userAgent;
427
+ #fetch;
428
+ #sleep;
429
+ #random;
430
+ constructor(options) {
431
+ if (!options.apiKey?.trim()) {
432
+ throw new QlooClientError("QLOO_CONFIGURATION_ERROR", "Qloo API key is required.");
433
+ }
434
+ this.#apiKey = options.apiKey;
435
+ this.#baseUrl = normalizeBaseUrl(options.baseUrl ?? QLOO_PRODUCTION_BASE_URL);
436
+ if (!isOfficialQlooBaseUrl(this.#baseUrl.toString()) && !options.allowCustomBaseUrl) {
437
+ throw new QlooClientError("QLOO_CONFIGURATION_ERROR", "Custom Qloo base URL is not trusted. Approve the exact endpoint before sending credentials.");
438
+ }
439
+ this.baseUrl = this.#baseUrl.toString().replace(/\/$/, "");
440
+ this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
441
+ this.#maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
442
+ this.#maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
443
+ this.#maxRetryDelayMs = options.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
444
+ this.#userAgent = options.userAgent ?? "agentic-qloo-client/0.1.0";
445
+ this.#fetch = options.fetch ?? globalThis.fetch;
446
+ this.#sleep = options.sleep ?? defaultSleep;
447
+ this.#random = options.random ?? Math.random;
448
+ validatePositiveInteger("timeoutMs", this.#timeoutMs);
449
+ validatePositiveInteger("maxAttempts", this.#maxAttempts);
450
+ validatePositiveInteger("maxResponseBytes", this.#maxResponseBytes);
451
+ validatePositiveInteger("maxRetryDelayMs", this.#maxRetryDelayMs);
452
+ }
453
+ async get(path, query = {}, options = {}) {
454
+ if (path.includes("\\") || path.startsWith("//") || /^[A-Za-z][A-Za-z\d+.-]*:/u.test(path) || /[?#\u0000-\u001f\u007f]/u.test(path)) {
455
+ throw new QlooClientError("QLOO_CONFIGURATION_ERROR", "Qloo request path must be a relative API path without a query or fragment.");
456
+ }
457
+ const relativePath = path.replace(/^\/+/, "");
458
+ const url = new URL(relativePath, this.#baseUrl);
459
+ if (url.origin !== this.#baseUrl.origin || !url.pathname.startsWith(this.#baseUrl.pathname)) {
460
+ throw new QlooClientError("QLOO_CONFIGURATION_ERROR", "Qloo request path cannot escape the trusted API base URL.");
461
+ }
462
+ appendQuery(url, query);
463
+ for (let attempt = 1; attempt <= this.#maxAttempts; attempt += 1) {
464
+ if (options.signal?.aborted)
465
+ throw options.signal.reason;
466
+ const attemptSignal = createAttemptSignal(options.signal, this.#timeoutMs);
467
+ let response;
468
+ let body;
469
+ try {
470
+ response = await this.#fetch(url, {
471
+ method: "GET",
472
+ headers: {
473
+ Accept: "application/json",
474
+ "X-Api-Key": this.#apiKey,
475
+ "User-Agent": this.#userAgent,
476
+ ...options.correlationId ? { "X-Correlation-Id": options.correlationId } : {}
477
+ },
478
+ signal: attemptSignal.signal
479
+ });
480
+ const text = await readBoundedBody(response, this.#maxResponseBytes);
481
+ body = parseJsonBody(text, response);
482
+ } catch (cause) {
483
+ const timedOut = attemptSignal.didTimeout();
484
+ attemptSignal.dispose();
485
+ if (options.signal?.aborted)
486
+ throw options.signal.reason;
487
+ if (cause instanceof QlooClientError) {
488
+ if (cause.retryable && attempt < this.#maxAttempts) {
489
+ await this.#sleep(this.#retryDelay(attempt), options.signal);
490
+ continue;
491
+ }
492
+ throw cause;
493
+ }
494
+ if (timedOut) {
495
+ if (attempt < this.#maxAttempts) {
496
+ await this.#sleep(this.#retryDelay(attempt), options.signal);
497
+ continue;
498
+ }
499
+ throw new QlooClientError("QLOO_TIMEOUT", "Qloo API request timed out.", {
500
+ cause,
501
+ retryable: true
502
+ });
503
+ }
504
+ if (attempt < this.#maxAttempts) {
505
+ await this.#sleep(this.#retryDelay(attempt), options.signal);
506
+ continue;
507
+ }
508
+ const failure = classifyQlooNetworkFailure(cause);
509
+ throw new QlooClientError(failure.code, failure.message, {
510
+ cause,
511
+ retryable: failure.retryable,
512
+ details: {
513
+ kind: failure.kind,
514
+ attempts: this.#maxAttempts,
515
+ ...failure.systemCode ? { systemCode: failure.systemCode } : {}
516
+ }
517
+ });
518
+ } finally {
519
+ attemptSignal.dispose();
520
+ }
521
+ if (response.ok)
522
+ return body;
523
+ const retryable = RETRYABLE_STATUS_CODES.has(response.status);
524
+ if (retryable && attempt < this.#maxAttempts) {
525
+ const retryAfter = retryAfterMilliseconds(response);
526
+ await this.#sleep(Math.min(retryAfter ?? this.#retryDelay(attempt), this.#maxRetryDelayMs), options.signal);
527
+ continue;
528
+ }
529
+ throw new QlooClientError("QLOO_HTTP_ERROR", responseMessage(body, response), {
530
+ retryable,
531
+ details: body,
532
+ ...responseErrorMetadata(response)
533
+ });
534
+ }
535
+ throw new QlooClientError("QLOO_NETWORK_ERROR", "Unable to reach the Qloo API.", {
536
+ retryable: true
537
+ });
538
+ }
539
+ apiInformation(options) {
540
+ return this.get("/", {}, options);
541
+ }
542
+ insights(query, options) {
543
+ return this.get("/v2/insights", query, options);
544
+ }
545
+ searchEntities(query, options) {
546
+ return this.get("/search", query, options);
547
+ }
548
+ entities(entityIds, options) {
549
+ return this.get("/entities", { entity_ids: entityIds }, options);
550
+ }
551
+ audiences(query = {}, options) {
552
+ return this.get("/v2/audiences", query, options);
553
+ }
554
+ audienceTypes(query = {}, options) {
555
+ return this.get("/v2/audiences/types", query, options);
556
+ }
557
+ tags(query = {}, options) {
558
+ return this.get("/v2/tags", query, options);
559
+ }
560
+ tagTypes(query = {}, options) {
561
+ return this.get("/v2/tags/types", query, options);
562
+ }
563
+ compare(query, options) {
564
+ return this.get("/v2/analysis/compare", query, options);
565
+ }
566
+ trending(query, options) {
567
+ return this.get("/v2/trending", query, options);
568
+ }
569
+ #retryDelay(attempt) {
570
+ const exponential = 250 * 2 ** (attempt - 1);
571
+ const jittered = exponential * (0.75 + this.#random() * 0.5);
572
+ return Math.min(Math.round(jittered), this.#maxRetryDelayMs);
573
+ }
574
+ };
575
+
576
+ // packages/qloo-tool-schema/dist/upstream.lock.json
577
+ var upstream_lock_default = {
578
+ lockVersion: 1,
579
+ source: {
580
+ documentationUrl: "https://docs.qloo.com",
581
+ registryUrl: "https://dash.readme.com/api/v1/api-registry/4svkj1svmrti5tfx",
582
+ registryId: "4svkj1svmrti5tfx",
583
+ retrievedAt: "2026-08-20",
584
+ apiTitle: "Qloo API",
585
+ apiVersion: "2.0",
586
+ openapiVersion: "3.1.0",
587
+ sha256: "da8d4b830f2a646815d25afcaa653f81f1ad939707e2e2560d2d0b9f103076f1"
588
+ },
589
+ expectations: {
590
+ productionServer: "https://api.qloo.com",
591
+ authentication: {
592
+ scheme: "ApiKey",
593
+ type: "apiKey",
594
+ location: "header",
595
+ header: "X-Api-Key"
596
+ },
597
+ getPaths: [
598
+ "/",
599
+ "/entities",
600
+ "/search",
601
+ "/v2/analysis/compare",
602
+ "/v2/audiences",
603
+ "/v2/audiences/types",
604
+ "/v2/insights",
605
+ "/v2/tags",
606
+ "/v2/tags/types",
607
+ "/v2/trending"
608
+ ]
609
+ },
610
+ documentedExceptions: [
611
+ {
612
+ value: "urn:demographics",
613
+ scope: "/v2/insights filter.type",
614
+ status: "documented-but-not-enumerated",
615
+ documentationUrl: "https://docs.qloo.com/reference/demographics-use-case",
616
+ treatment: "Preserve as an explicit documentation exception; do not synthesize an endpoint payload schema from it."
617
+ },
618
+ {
619
+ value: "urn:tag",
620
+ scope: "/v2/insights filter.type",
621
+ status: "documented-but-not-enumerated",
622
+ documentationUrl: "https://docs.qloo.com/reference/taste-analysis",
623
+ treatment: "The pinned specification mentions this value in diversify.by documentation but omits it from the filter.type enum; preserve the discrepancy for review."
624
+ }
625
+ ]
626
+ };
627
+
628
+ // packages/qloo-tool-schema/dist/src/catalog.js
629
+ var QLOO_TOOL_CATALOG_VERSION = "1.0.0";
630
+ var QLOO_TOOL_RESULT_SCHEMA_VERSION = "1.0-preview.1";
631
+ var QLOO_WORKFLOW_OPERATION_IDS = [
632
+ "recommend",
633
+ "rank",
634
+ "describe",
635
+ "where_popular",
636
+ "compare_audiences",
637
+ "entity_tags",
638
+ "audience_demographics",
639
+ "trends",
640
+ "find_tags"
641
+ ];
642
+ var QLOO_CAPABILITIES_TOOL_NAME = "qloo_capabilities";
643
+ var QLOO_INSIGHTS_ENTITY_TYPES = [
644
+ { id: "artist", urn: "urn:entity:artist" },
645
+ { id: "book", urn: "urn:entity:book" },
646
+ { id: "brand", urn: "urn:entity:brand" },
647
+ { id: "movie", urn: "urn:entity:movie" },
648
+ { id: "person", urn: "urn:entity:person" },
649
+ { id: "place", urn: "urn:entity:place" },
650
+ { id: "podcast", urn: "urn:entity:podcast" },
651
+ { id: "tv_show", urn: "urn:entity:tv_show" },
652
+ { id: "videogame", urn: "urn:entity:videogame" }
653
+ ];
654
+ var QLOO_SEARCH_ENTITY_TYPES = [
655
+ ...QLOO_INSIGHTS_ENTITY_TYPES,
656
+ { id: "locality", urn: "urn:entity:locality" },
657
+ { id: "actor", urn: "urn:entity:actor" },
658
+ { id: "album", urn: "urn:entity:album" },
659
+ { id: "author", urn: "urn:entity:author" },
660
+ { id: "director", urn: "urn:entity:director" }
661
+ ];
662
+ function buildEntityTypeAliases(entityTypes) {
663
+ const aliases = /* @__PURE__ */ new Map();
664
+ const urnById = new Map(entityTypes.map(({ id, urn }) => [id, urn]));
665
+ for (const entityType of entityTypes) {
666
+ aliases.set(entityType.id, entityType.urn);
667
+ aliases.set(entityType.urn, entityType.urn);
668
+ aliases.set(`${entityType.id}s`, entityType.urn);
669
+ }
670
+ const addAlias = (alias, id) => {
671
+ const urn = urnById.get(id);
672
+ if (urn)
673
+ aliases.set(alias, urn);
674
+ };
675
+ addAlias("people", "person");
676
+ addAlias("localities", "locality");
677
+ addAlias("tv", "tv_show");
678
+ addAlias("show", "tv_show");
679
+ addAlias("shows", "tv_show");
680
+ addAlias("tv show", "tv_show");
681
+ addAlias("tv shows", "tv_show");
682
+ addAlias("game", "videogame");
683
+ addAlias("games", "videogame");
684
+ addAlias("video game", "videogame");
685
+ addAlias("video games", "videogame");
686
+ return aliases;
687
+ }
688
+ var insightsEntityTypeByAlias = buildEntityTypeAliases(QLOO_INSIGHTS_ENTITY_TYPES);
689
+ var searchEntityTypeByAlias = buildEntityTypeAliases(QLOO_SEARCH_ENTITY_TYPES);
690
+ function resolveQlooInsightsEntityType(value) {
691
+ return insightsEntityTypeByAlias.get(value.trim().toLowerCase());
692
+ }
693
+ function resolveQlooSearchEntityType(value) {
694
+ return searchEntityTypeByAlias.get(value.trim().toLowerCase());
695
+ }
696
+ var operationSummaries = {
697
+ recommend: "Find entities for a taste profile, audience, or collection of signals.",
698
+ rank: "Rank a supplied set of options for signals, location, or audience.",
699
+ describe: "Resolve and describe one Qloo entity.",
700
+ where_popular: "Find geographic areas where an entity has comparatively strong affinity.",
701
+ compare_audiences: "Contrast two signal groups and identify shared or differentiating affinities.",
702
+ entity_tags: "Retrieve concepts and tags that characterize entities.",
703
+ audience_demographics: "Retrieve aggregate audience segments associated with an entity.",
704
+ trends: "Retrieve interest or trend information over a time range.",
705
+ find_tags: "Search the Qloo tag ontology by natural-language concept."
706
+ };
707
+ var QLOO_WORKFLOW_OPERATIONS = Object.freeze(QLOO_WORKFLOW_OPERATION_IDS.map((id) => Object.freeze({
708
+ id,
709
+ toolName: `qloo_${id}`,
710
+ summary: operationSummaries[id],
711
+ since: QLOO_TOOL_CATALOG_VERSION
712
+ })));
713
+ var upstreamLock = upstream_lock_default;
714
+ var upstreamGetPaths = Object.freeze([
715
+ ...upstreamLock.expectations.getPaths
716
+ ]);
717
+ var documentedExceptions = Object.freeze(upstreamLock.documentedExceptions.map((exception) => Object.freeze({ ...exception })));
718
+ var QLOO_CAPABILITIES_TOOL = Object.freeze({
719
+ name: QLOO_CAPABILITIES_TOOL_NAME,
720
+ description: "Return the versioned Qloo workflow catalog and pinned upstream API contract metadata without making a network request.",
721
+ inputSchema: Object.freeze({
722
+ $schema: "https://json-schema.org/draft/2020-12/schema",
723
+ type: "object",
724
+ additionalProperties: false,
725
+ properties: Object.freeze({
726
+ operation_ids: Object.freeze({
727
+ type: "array",
728
+ description: "Optional canonical operation IDs to include. Omit for the complete catalog.",
729
+ uniqueItems: true,
730
+ items: Object.freeze({
731
+ type: "string",
732
+ enum: QLOO_WORKFLOW_OPERATION_IDS
733
+ })
734
+ })
735
+ })
736
+ })
737
+ });
738
+ var operationIds = new Set(QLOO_WORKFLOW_OPERATION_IDS);
739
+ function isQlooWorkflowOperationId(value) {
740
+ return operationIds.has(value);
741
+ }
742
+ function getQlooCapabilities(input = {}) {
743
+ const selected = input.operation_ids ? new Set(input.operation_ids) : void 0;
744
+ return {
745
+ contract_version: QLOO_TOOL_CATALOG_VERSION,
746
+ introspection_tool: QLOO_CAPABILITIES_TOOL_NAME,
747
+ operations: selected ? QLOO_WORKFLOW_OPERATIONS.filter(({ id }) => selected.has(id)) : QLOO_WORKFLOW_OPERATIONS,
748
+ upstream: {
749
+ documentation_url: upstreamLock.source.documentationUrl,
750
+ api_version: upstreamLock.source.apiVersion,
751
+ openapi_version: upstreamLock.source.openapiVersion,
752
+ production_server: upstreamLock.expectations.productionServer,
753
+ lock_sha256: upstreamLock.source.sha256,
754
+ get_paths: upstreamGetPaths,
755
+ documented_exceptions: documentedExceptions
756
+ }
757
+ };
758
+ }
759
+
760
+ // packages/qloo-tool-schema/dist/src/workflow-contracts.js
761
+ import { Type } from "typebox";
762
+ import { Value } from "typebox/value";
763
+ var QLOO_TOOL_DEFAULT_LIMIT = 10;
764
+ var QLOO_TOOL_MAX_RESULTS = 20;
765
+ var QLOO_TAG_BOOLEAN_OPERATORS = ["union", "intersection"];
766
+ var insightsEntityTypeIds = QLOO_INSIGHTS_ENTITY_TYPES.map(({ id }) => id);
767
+ var searchEntityTypeIds = QLOO_SEARCH_ENTITY_TYPES.map(({ id }) => id);
768
+ var QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA = Type.String({
769
+ enum: insightsEntityTypeIds,
770
+ description: `Qloo Insights entity type: ${insightsEntityTypeIds.join(", ")}.`
771
+ });
772
+ var QLOO_SEARCH_ENTITY_TYPE_SCHEMA = Type.String({
773
+ enum: searchEntityTypeIds,
774
+ description: `Qloo search entity type: ${searchEntityTypeIds.join(", ")}.`
775
+ });
776
+ var QLOO_TAG_BOOLEAN_OPERATOR_SCHEMA = Type.String({
777
+ enum: QLOO_TAG_BOOLEAN_OPERATORS,
778
+ description: "Combine tag values with union (any) or intersection (all)."
779
+ });
780
+ var QLOO_LIMIT_SCHEMA = Type.Optional(Type.Integer({
781
+ minimum: 1,
782
+ maximum: QLOO_TOOL_MAX_RESULTS,
783
+ default: QLOO_TOOL_DEFAULT_LIMIT,
784
+ description: `Maximum results to return (1-${QLOO_TOOL_MAX_RESULTS}).`
785
+ }));
786
+ var QLOO_ENTITY_INPUTS_SCHEMA = Type.Array(Type.String({ minLength: 1 }), {
787
+ minItems: 1,
788
+ maxItems: 10,
789
+ description: "Entity names or Qloo entity UUIDs. Keep one combined taste profile in one call."
790
+ });
791
+ var QLOO_TAG_INPUTS_SCHEMA = Type.Array(Type.String({ minLength: 1 }), {
792
+ minItems: 1,
793
+ maxItems: 10,
794
+ description: "Natural-language tag concepts or stable Qloo tag URNs."
795
+ });
796
+ var QLOO_DEMOGRAPHIC_SCHEMA = Type.String({
797
+ minLength: 1,
798
+ description: "Audience demographic in natural language, such as 'young women', 'men 25-34', or 'millennials'. It is converted to documented Qloo demographic signals.",
799
+ examples: ["young women", "men 25-34", "millennials"]
800
+ });
801
+ var QLOO_REQUEST_PREVIEW_SCHEMA = Type.Object({
802
+ method: Type.Literal("GET"),
803
+ path: Type.String({ minLength: 1 }),
804
+ query: Type.Unknown()
805
+ }, { additionalProperties: false });
806
+ var QLOO_RESULT_ENVELOPE_SCHEMA = Type.Object({
807
+ schema_version: Type.Literal(QLOO_TOOL_RESULT_SCHEMA_VERSION),
808
+ operation: Type.String(),
809
+ status: Type.Union([
810
+ Type.Literal("ok"),
811
+ Type.Literal("empty"),
812
+ Type.Literal("needs_input"),
813
+ Type.Literal("partial"),
814
+ Type.Literal("degraded"),
815
+ Type.Literal("error")
816
+ ]),
817
+ summary: Type.Optional(Type.String()),
818
+ interpretation: Type.Optional(Type.Unknown()),
819
+ results: Type.Optional(Type.Unknown()),
820
+ result_count: Type.Optional(Type.Integer({ minimum: 0 })),
821
+ warnings: Type.Optional(Type.Array(Type.String())),
822
+ query_intent: Type.Optional(Type.Unknown()),
823
+ error: Type.Optional(Type.Object({
824
+ code: Type.String(),
825
+ layer: Type.String(),
826
+ retryable: Type.Boolean(),
827
+ recovery: Type.String()
828
+ }, { additionalProperties: false })),
829
+ execution: Type.Object({
830
+ transport: Type.String({ enum: ["direct", "cli", "mcp"] }),
831
+ transport_name: Type.String(),
832
+ correlation_id: Type.String(),
833
+ duration_ms: Type.Number({ minimum: 0 })
834
+ }, { additionalProperties: false }),
835
+ provenance: Type.Optional(Type.Object({
836
+ source: Type.String(),
837
+ endpoint: Type.String(),
838
+ documentation: Type.String(),
839
+ transport: Type.Optional(Type.String()),
840
+ requests: Type.Optional(Type.Array(QLOO_REQUEST_PREVIEW_SCHEMA, {
841
+ maxItems: 20
842
+ }))
843
+ }, { additionalProperties: false }))
844
+ }, { additionalProperties: true });
845
+ var QLOO_WORKFLOW_INPUT_SCHEMAS = {
846
+ recommend: Type.Object({
847
+ target_type: QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA,
848
+ signals: Type.Optional(QLOO_ENTITY_INPUTS_SCHEMA),
849
+ signal_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA),
850
+ signal_tags_operator: Type.Optional(QLOO_TAG_BOOLEAN_OPERATOR_SCHEMA),
851
+ signal_location: Type.Optional(Type.String({ minLength: 1 })),
852
+ demographic: Type.Optional(QLOO_DEMOGRAPHIC_SCHEMA),
853
+ filter_location: Type.Optional(Type.String({ minLength: 1 })),
854
+ include_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA),
855
+ include_tags_operator: Type.Optional(QLOO_TAG_BOOLEAN_OPERATOR_SCHEMA),
856
+ exclude_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA),
857
+ exclude_tags_operator: Type.Optional(QLOO_TAG_BOOLEAN_OPERATOR_SCHEMA),
858
+ explain: Type.Optional(Type.Boolean({ default: true })),
859
+ limit: QLOO_LIMIT_SCHEMA
860
+ }, { additionalProperties: false }),
861
+ rank: Type.Object({
862
+ options: QLOO_ENTITY_INPUTS_SCHEMA,
863
+ option_type: QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA,
864
+ signals: Type.Optional(QLOO_ENTITY_INPUTS_SCHEMA),
865
+ signal_location: Type.Optional(Type.String({ minLength: 1 })),
866
+ demographic: Type.Optional(QLOO_DEMOGRAPHIC_SCHEMA),
867
+ include_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA),
868
+ exclude_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA)
869
+ }, { additionalProperties: false }),
870
+ describe: Type.Object({
871
+ entity: Type.String({ minLength: 1 }),
872
+ type: Type.Optional(QLOO_SEARCH_ENTITY_TYPE_SCHEMA)
873
+ }, { additionalProperties: false }),
874
+ where_popular: Type.Object({
875
+ entity: Type.String({ minLength: 1 }),
876
+ entity_type: Type.Optional(QLOO_SEARCH_ENTITY_TYPE_SCHEMA),
877
+ within: Type.String({
878
+ minLength: 1,
879
+ description: "Required geographic area that constrains the heatmap."
880
+ }),
881
+ limit: QLOO_LIMIT_SCHEMA
882
+ }, { additionalProperties: false }),
883
+ compare_audiences: Type.Object({
884
+ group_a: QLOO_ENTITY_INPUTS_SCHEMA,
885
+ group_b: QLOO_ENTITY_INPUTS_SCHEMA,
886
+ target_type: Type.Optional(QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA),
887
+ limit: QLOO_LIMIT_SCHEMA
888
+ }, { additionalProperties: false }),
889
+ entity_tags: Type.Object({
890
+ entities: QLOO_ENTITY_INPUTS_SCHEMA,
891
+ entity_type: Type.Optional(QLOO_SEARCH_ENTITY_TYPE_SCHEMA),
892
+ limit: QLOO_LIMIT_SCHEMA
893
+ }, { additionalProperties: false }),
894
+ audience_demographics: Type.Object({
895
+ entity: Type.String({ minLength: 1 }),
896
+ entity_type: Type.Optional(QLOO_SEARCH_ENTITY_TYPE_SCHEMA),
897
+ limit: QLOO_LIMIT_SCHEMA
898
+ }, { additionalProperties: false }),
899
+ trends: Type.Object({
900
+ entities: Type.Array(Type.String({ minLength: 1 }), { minItems: 1, maxItems: 5 }),
901
+ entity_type: QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA,
902
+ start_date: Type.String({
903
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
904
+ description: "Inclusive start date in YYYY-MM-DD format."
905
+ }),
906
+ end_date: Type.String({
907
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
908
+ description: "Inclusive end date in YYYY-MM-DD format."
909
+ }),
910
+ limit: QLOO_LIMIT_SCHEMA
911
+ }, { additionalProperties: false }),
912
+ find_tags: Type.Object({
913
+ query: Type.String({ minLength: 1 }),
914
+ semantic: Type.Optional(Type.Boolean({ default: true })),
915
+ limit: QLOO_LIMIT_SCHEMA
916
+ }, { additionalProperties: false })
917
+ };
918
+ var sharedWorkflowBehavior = Object.freeze({
919
+ readOnly: true,
920
+ idempotent: true,
921
+ mutating: false,
922
+ destructive: false,
923
+ openWorld: true,
924
+ concurrency: "safe_for_independent_inputs",
925
+ requiredCredential: "qloo_api",
926
+ defaultResultBudget: QLOO_TOOL_DEFAULT_LIMIT,
927
+ maximumResultBudget: QLOO_TOOL_MAX_RESULTS,
928
+ safeRetryClasses: Object.freeze(["dns", "connect", "timeout", "rate_limit", "upstream_5xx"]),
929
+ supportedTransports: Object.freeze(["direct", "mcp"])
930
+ });
931
+ var mediumLatencyOperations = /* @__PURE__ */ new Set([
932
+ "recommend",
933
+ "rank",
934
+ "compare_audiences",
935
+ "entity_tags",
936
+ "audience_demographics",
937
+ "trends"
938
+ ]);
939
+ var QLOO_WORKFLOW_BEHAVIORS = Object.freeze(Object.fromEntries(Object.keys(QLOO_WORKFLOW_INPUT_SCHEMAS).map((operation) => [
940
+ operation,
941
+ Object.freeze({
942
+ ...sharedWorkflowBehavior,
943
+ expectedLatency: mediumLatencyOperations.has(operation) ? "medium" : "low"
944
+ })
945
+ ])));
946
+ var QLOO_WORKFLOW_METADATA = {
947
+ recommend: {
948
+ toolName: "qloo_recommend",
949
+ label: "Qloo recommendations",
950
+ description: "Find Qloo entities related to one combined entity, tag, location, and demographic taste profile. Signals influence affinity; filters constrain output.",
951
+ promptSnippet: "Recommend entities from a combined Qloo taste profile",
952
+ promptGuidelines: [
953
+ "Pass every related taste and audience signal together in one qloo_recommend call.",
954
+ "Use signal_location for audience location and filter_location to constrain result geography.",
955
+ "Include demographic and location in the same call when both describe the audience."
956
+ ],
957
+ documentation: ["https://docs.qloo.com/reference/insights-api-deep-dive"]
958
+ },
959
+ rank: {
960
+ toolName: "qloo_rank",
961
+ label: "Qloo rank",
962
+ description: "Rank one supplied option set against one shared entity, location, demographic, and tag profile. Scores from separate calls are not comparable.",
963
+ promptSnippet: "Rank a caller-supplied shortlist with Qloo",
964
+ promptGuidelines: ["Pass every option in one qloo_rank call so scores remain comparable."],
965
+ documentation: ["https://docs.qloo.com/reference/insights-api-deep-dive"]
966
+ },
967
+ describe: {
968
+ toolName: "qloo_describe",
969
+ label: "Qloo entity",
970
+ description: "Resolve one named entity or Qloo UUID and return only metadata present in Qloo.",
971
+ promptSnippet: "Resolve and describe one Qloo entity",
972
+ documentation: ["https://docs.qloo.com/reference/get-search", "https://docs.qloo.com/reference/get-entities"]
973
+ },
974
+ where_popular: {
975
+ toolName: "qloo_where_popular",
976
+ label: "Qloo geographic affinity",
977
+ description: "Find geographic areas where one entity has strong query-relative affinity. This is distinct from filtering recommendations to a place.",
978
+ promptSnippet: "Find where an entity is comparatively popular",
979
+ documentation: ["https://docs.qloo.com/reference/heatmaps-use-case"]
980
+ },
981
+ compare_audiences: {
982
+ toolName: "qloo_compare_audiences",
983
+ label: "Qloo audience comparison",
984
+ description: "Compare two complete groups of entity signals in one Qloo analysis request.",
985
+ promptSnippet: "Compare two Qloo taste-signal groups",
986
+ documentation: ["https://docs.qloo.com/reference/analysis-compare"]
987
+ },
988
+ entity_tags: {
989
+ toolName: "qloo_entity_tags",
990
+ label: "Qloo entity tags",
991
+ description: "Retrieve Qloo concepts that jointly characterize one or more entities using the documented urn:tag Insights workflow.",
992
+ promptSnippet: "Find concepts that characterize Qloo entities",
993
+ documentation: ["https://docs.qloo.com/reference/taste-analysis"]
994
+ },
995
+ audience_demographics: {
996
+ toolName: "qloo_audience_demographics",
997
+ label: "Qloo audience demographics",
998
+ description: "Retrieve aggregate demographic distributions associated with one entity through the documented urn:demographics Insights workflow.",
999
+ promptSnippet: "Inspect aggregate demographics associated with a Qloo entity",
1000
+ documentation: ["https://docs.qloo.com/reference/demographics-use-case"]
1001
+ },
1002
+ trends: {
1003
+ toolName: "qloo_trends",
1004
+ label: "Qloo trends",
1005
+ description: "Retrieve Qloo time-series popularity metrics for one to five resolved entities over an ISO date range.",
1006
+ promptSnippet: "Track Qloo entity popularity metrics over time",
1007
+ documentation: ["https://docs.qloo.com/reference/get-trending"]
1008
+ },
1009
+ find_tags: {
1010
+ toolName: "qloo_find_tags",
1011
+ label: "Qloo tag search",
1012
+ description: "Search Qloo's tag ontology by natural-language concept and return stable tag URNs.",
1013
+ promptSnippet: "Search the Qloo tag ontology",
1014
+ documentation: ["https://docs.qloo.com/reference/get-tags-1"]
1015
+ }
1016
+ };
1017
+ var QLOO_WORKFLOW_CONTRACTS = Object.freeze(Object.fromEntries(Object.entries(QLOO_WORKFLOW_METADATA).map(([id, metadata]) => [
1018
+ id,
1019
+ Object.freeze({
1020
+ id,
1021
+ ...metadata,
1022
+ behavior: QLOO_WORKFLOW_BEHAVIORS[id],
1023
+ inputSchema: QLOO_WORKFLOW_INPUT_SCHEMAS[id],
1024
+ resultSchema: QLOO_RESULT_ENVELOPE_SCHEMA
1025
+ })
1026
+ ])));
1027
+ var QLOO_ADAPTER_CONTRACT_MANIFEST_SCHEMA_VERSION = "1.1";
1028
+ function getQlooExecutableCapabilities(input = {}) {
1029
+ const capabilities = getQlooCapabilities(input);
1030
+ return {
1031
+ ...capabilities,
1032
+ result_schema_version: QLOO_TOOL_RESULT_SCHEMA_VERSION,
1033
+ operations: capabilities.operations.map((operation) => {
1034
+ const contract = QLOO_WORKFLOW_CONTRACTS[operation.id];
1035
+ return {
1036
+ ...operation,
1037
+ input_schema: contract.inputSchema,
1038
+ result_schema: contract.resultSchema,
1039
+ documentation: contract.documentation,
1040
+ behavior: contract.behavior
1041
+ };
1042
+ })
1043
+ };
1044
+ }
1045
+ var QLOO_QUERY_INTENT_VERSION = "1.0-preview.1";
1046
+ function createQlooQueryIntent(operation, input) {
1047
+ const base = {
1048
+ schema_version: QLOO_QUERY_INTENT_VERSION,
1049
+ source_contract_version: QLOO_TOOL_CATALOG_VERSION,
1050
+ operation,
1051
+ job: operation,
1052
+ signals: {},
1053
+ filters: {},
1054
+ operators: {},
1055
+ output: {},
1056
+ assumptions: [],
1057
+ unresolved: []
1058
+ };
1059
+ switch (operation) {
1060
+ case "recommend": {
1061
+ const value = input;
1062
+ return {
1063
+ ...base,
1064
+ target: { type: value.target_type },
1065
+ signals: {
1066
+ ...value.signals ? { entities: value.signals } : {},
1067
+ ...value.signal_tags ? { tags: value.signal_tags } : {},
1068
+ ...value.signal_location ? { location: value.signal_location } : {},
1069
+ ...value.demographic ? { demographic: value.demographic } : {}
1070
+ },
1071
+ filters: {
1072
+ ...value.include_tags ? { includeTags: value.include_tags } : {},
1073
+ ...value.exclude_tags ? { excludeTags: value.exclude_tags } : {},
1074
+ ...value.filter_location ? { location: value.filter_location } : {}
1075
+ },
1076
+ operators: {
1077
+ ...value.signal_tags ? { signalTags: value.signal_tags_operator ?? "intersection" } : {},
1078
+ ...value.include_tags ? { includeTags: value.include_tags_operator ?? "union" } : {},
1079
+ ...value.exclude_tags ? { excludeTags: value.exclude_tags_operator ?? "union" } : {}
1080
+ },
1081
+ output: { limit: value.limit ?? QLOO_TOOL_DEFAULT_LIMIT, explain: value.explain ?? true },
1082
+ assumptions: [
1083
+ ...value.limit === void 0 ? [`limit=${QLOO_TOOL_DEFAULT_LIMIT}`] : [],
1084
+ ...value.explain === void 0 ? ["explain=true"] : []
1085
+ ]
1086
+ };
1087
+ }
1088
+ case "rank": {
1089
+ const value = input;
1090
+ return {
1091
+ ...base,
1092
+ target: { type: value.option_type },
1093
+ signals: {
1094
+ ...value.signals ? { entities: value.signals } : {},
1095
+ ...value.signal_location ? { location: value.signal_location } : {},
1096
+ ...value.demographic ? { demographic: value.demographic } : {}
1097
+ },
1098
+ filters: {
1099
+ candidateEntities: value.options,
1100
+ ...value.include_tags ? { includeTags: value.include_tags } : {},
1101
+ ...value.exclude_tags ? { excludeTags: value.exclude_tags } : {}
1102
+ },
1103
+ operators: {
1104
+ ...value.include_tags ? { includeTags: "union" } : {},
1105
+ ...value.exclude_tags ? { excludeTags: "union" } : {}
1106
+ },
1107
+ output: { limit: value.options.length },
1108
+ assumptions: []
1109
+ };
1110
+ }
1111
+ case "describe": {
1112
+ const value = input;
1113
+ return { ...base, target: { ...value.type ? { type: value.type } : {}, entities: [value.entity] } };
1114
+ }
1115
+ case "where_popular": {
1116
+ const value = input;
1117
+ return {
1118
+ ...base,
1119
+ target: { ...value.entity_type ? { type: value.entity_type } : {}, entities: [value.entity] },
1120
+ filters: { location: value.within },
1121
+ output: { limit: value.limit ?? QLOO_TOOL_DEFAULT_LIMIT },
1122
+ assumptions: value.limit === void 0 ? [`limit=${QLOO_TOOL_DEFAULT_LIMIT}`] : []
1123
+ };
1124
+ }
1125
+ case "compare_audiences": {
1126
+ const value = input;
1127
+ return {
1128
+ ...base,
1129
+ ...value.target_type ? { target: { type: value.target_type } } : {},
1130
+ signals: { groups: [value.group_a, value.group_b] },
1131
+ output: { limit: value.limit ?? QLOO_TOOL_DEFAULT_LIMIT },
1132
+ assumptions: value.limit === void 0 ? [`limit=${QLOO_TOOL_DEFAULT_LIMIT}`] : []
1133
+ };
1134
+ }
1135
+ case "entity_tags": {
1136
+ const value = input;
1137
+ return {
1138
+ ...base,
1139
+ ...value.entity_type ? { target: { type: value.entity_type } } : {},
1140
+ signals: { entities: value.entities },
1141
+ output: { limit: value.limit ?? QLOO_TOOL_DEFAULT_LIMIT },
1142
+ assumptions: value.limit === void 0 ? [`limit=${QLOO_TOOL_DEFAULT_LIMIT}`] : []
1143
+ };
1144
+ }
1145
+ case "audience_demographics": {
1146
+ const value = input;
1147
+ return {
1148
+ ...base,
1149
+ target: value.entity_type ? { type: value.entity_type, entities: [value.entity] } : { entities: [value.entity] },
1150
+ output: { limit: value.limit ?? QLOO_TOOL_DEFAULT_LIMIT },
1151
+ assumptions: value.limit === void 0 ? [`limit=${QLOO_TOOL_DEFAULT_LIMIT}`] : []
1152
+ };
1153
+ }
1154
+ case "trends": {
1155
+ const value = input;
1156
+ return {
1157
+ ...base,
1158
+ target: { type: value.entity_type, entities: value.entities },
1159
+ filters: { startDate: value.start_date, endDate: value.end_date },
1160
+ output: { limit: value.limit ?? QLOO_TOOL_DEFAULT_LIMIT },
1161
+ assumptions: value.limit === void 0 ? [`limit=${QLOO_TOOL_DEFAULT_LIMIT}`] : []
1162
+ };
1163
+ }
1164
+ case "find_tags": {
1165
+ const value = input;
1166
+ return {
1167
+ ...base,
1168
+ target: { concept: value.query },
1169
+ output: { limit: value.limit ?? QLOO_TOOL_DEFAULT_LIMIT },
1170
+ assumptions: [
1171
+ ...value.semantic === void 0 ? ["semantic=true"] : [],
1172
+ ...value.limit === void 0 ? [`limit=${QLOO_TOOL_DEFAULT_LIMIT}`] : []
1173
+ ]
1174
+ };
1175
+ }
1176
+ }
1177
+ }
1178
+ function ageBucketsForRange(low, high) {
1179
+ const ranges = [
1180
+ [0, 24, "24_and_younger"],
1181
+ [25, 29, "25_to_29"],
1182
+ [30, 34, "30_to_34"],
1183
+ [35, 44, "35_to_44"],
1184
+ [45, 54, "45_to_54"],
1185
+ [55, 120, "55_and_older"]
1186
+ ];
1187
+ const matches = ranges.filter(([bucketLow, bucketHigh]) => low <= bucketHigh && high >= bucketLow).map(([, , name]) => name);
1188
+ return matches.join(",");
1189
+ }
1190
+ function parseQlooDemographic(value) {
1191
+ const normalized = value.normalize("NFKC").trim().toLocaleLowerCase("en-US");
1192
+ const result = {};
1193
+ if (/\b(women|woman|female|ladies)\b/u.test(normalized))
1194
+ result.gender = "female";
1195
+ else if (/\b(men|man|male|guys)\b/u.test(normalized))
1196
+ result.gender = "male";
1197
+ const explicitRange = normalized.match(/\b(\d{1,2})\s*(?:-|–|to)\s*(\d{1,2})\b/u);
1198
+ if (explicitRange) {
1199
+ const low = Number(explicitRange[1]);
1200
+ const high = Number(explicitRange[2]);
1201
+ if (low <= high)
1202
+ result.age = ageBucketsForRange(low, high);
1203
+ } else if (/\b(teenagers?|teens?|gen[\s-]?z)\b/u.test(normalized)) {
1204
+ result.age = "24_and_younger";
1205
+ } else if (/\byoung\b/u.test(normalized)) {
1206
+ result.age = "24_and_younger,25_to_29";
1207
+ } else if (/\bmillennials?\b/u.test(normalized)) {
1208
+ result.age = "30_to_34,35_to_44";
1209
+ } else if (/\b(seniors?|elderly|retirees?)\b/u.test(normalized)) {
1210
+ result.age = "55_and_older";
1211
+ } else if (/\bmiddle[\s-]?aged?\b/u.test(normalized)) {
1212
+ result.age = "35_to_44,45_to_54";
1213
+ } else {
1214
+ const over = normalized.match(/\b(?:over|older than|above)\s*(\d{1,2})\b/u);
1215
+ const under = normalized.match(/\b(?:under|younger than|below)\s*(\d{1,2})\b/u);
1216
+ if (over)
1217
+ result.age = ageBucketsForRange(Number(over[1]), 120);
1218
+ else if (under)
1219
+ result.age = ageBucketsForRange(0, Number(under[1]));
1220
+ }
1221
+ return result;
1222
+ }
1223
+
1224
+ // packages/qloo-tool-schema/dist/src/resolution-contracts.js
1225
+ import { Type as Type2 } from "typebox";
1226
+ var QLOO_RESOLUTION_CONTRACT_VERSION = "1.0-preview.1";
1227
+ var QLOO_RESOLUTION_PROVIDER_DESCRIPTOR_SCHEMA = Type2.Object({
1228
+ contract_version: Type2.Literal(QLOO_RESOLUTION_CONTRACT_VERSION),
1229
+ provider_id: Type2.String({ minLength: 1 }),
1230
+ name: Type2.String({ minLength: 1 }),
1231
+ entity_strategy: Type2.String({ minLength: 1 }),
1232
+ tag_strategy: Type2.String({ minLength: 1 }),
1233
+ remote: Type2.Boolean(),
1234
+ uses_model: Type2.Boolean(),
1235
+ fallback: Type2.Literal("none")
1236
+ }, { additionalProperties: false });
1237
+ var QLOO_RESOLUTION_CANDIDATE_SCHEMA = Type2.Object({
1238
+ id: Type2.String({ minLength: 1 }),
1239
+ name: Type2.String({ minLength: 1 }),
1240
+ type: Type2.Optional(Type2.String({ minLength: 1 })),
1241
+ rank: Type2.Optional(Type2.Integer({ minimum: 1 })),
1242
+ score: Type2.Optional(Type2.Number()),
1243
+ popularity: Type2.Optional(Type2.Number()),
1244
+ parent_types: Type2.Optional(Type2.Array(Type2.String({ minLength: 1 }), {
1245
+ maxItems: 20,
1246
+ uniqueItems: true
1247
+ })),
1248
+ description: Type2.Optional(Type2.String({ minLength: 1, maxLength: 500 })),
1249
+ release_year: Type2.Optional(Type2.Integer()),
1250
+ address: Type2.Optional(Type2.Unknown())
1251
+ }, { additionalProperties: false });
1252
+ var QLOO_RESOLUTION_PROVENANCE_SCHEMA = Type2.Object({
1253
+ contract_version: Type2.Literal(QLOO_RESOLUTION_CONTRACT_VERSION),
1254
+ provider_id: Type2.String({ minLength: 1 }),
1255
+ provider_name: Type2.String({ minLength: 1 }),
1256
+ endpoint: Type2.String({ minLength: 1 }),
1257
+ strategy: Type2.String({ minLength: 1 }),
1258
+ remote: Type2.Boolean(),
1259
+ uses_model: Type2.Boolean(),
1260
+ provider_version: Type2.Optional(Type2.String({ minLength: 1 })),
1261
+ cache_status: Type2.Optional(Type2.String({ minLength: 1 }))
1262
+ }, { additionalProperties: false });
1263
+ var QLOO_RESOLUTION_OUTCOME_COMMON_SCHEMA = {
1264
+ input: Type2.String({ minLength: 1 }),
1265
+ provenance: QLOO_RESOLUTION_PROVENANCE_SCHEMA,
1266
+ warnings: Type2.Optional(Type2.Array(Type2.String()))
1267
+ };
1268
+ var QLOO_RESOLUTION_OUTCOME_SCHEMA = Type2.Union([
1269
+ Type2.Object({
1270
+ ...QLOO_RESOLUTION_OUTCOME_COMMON_SCHEMA,
1271
+ status: Type2.Literal("resolved"),
1272
+ match: Type2.Union([
1273
+ Type2.Literal("identifier"),
1274
+ Type2.Literal("exact"),
1275
+ Type2.Literal("semantic")
1276
+ ]),
1277
+ selected: QLOO_RESOLUTION_CANDIDATE_SCHEMA,
1278
+ alternatives: Type2.Optional(Type2.Array(QLOO_RESOLUTION_CANDIDATE_SCHEMA))
1279
+ }, { additionalProperties: false }),
1280
+ Type2.Object({
1281
+ ...QLOO_RESOLUTION_OUTCOME_COMMON_SCHEMA,
1282
+ status: Type2.Literal("ambiguous"),
1283
+ candidates: Type2.Array(QLOO_RESOLUTION_CANDIDATE_SCHEMA)
1284
+ }, { additionalProperties: false }),
1285
+ Type2.Object({
1286
+ ...QLOO_RESOLUTION_OUTCOME_COMMON_SCHEMA,
1287
+ status: Type2.Literal("not_found"),
1288
+ candidates: Type2.Optional(Type2.Array(QLOO_RESOLUTION_CANDIDATE_SCHEMA))
1289
+ }, { additionalProperties: false })
1290
+ ]);
1291
+
1292
+ // apps/qloo-harness/dist/qloo-tools.js
1293
+ import { Type as Type3 } from "typebox";
1294
+ import { createDelegatingQlooWorkflowExecutor, QlooWorkflowExecutionError } from "./workflow-executor.js";
1295
+ import { createFileExecutionObserver } from "./observability.js";
1296
+ import { resolveQlooPaths } from "./paths.js";
1297
+ import { createQlooCallComponent, createQlooResultComponent } from "./qloo-presentation.js";
1298
+ import { createPublicQlooResolutionProvider, createQlooResolutionProviderFromEnvironment } from "./resolution-provider.js";
1299
+ function defineTool(tool) {
1300
+ return tool;
1301
+ }
1302
+ var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
1303
+ var DEFAULT_LIMIT = QLOO_TOOL_DEFAULT_LIMIT;
1304
+ var MAX_TOOL_RESULTS = QLOO_TOOL_MAX_RESULTS;
1305
+ var insightsEntityTypeIds2 = QLOO_INSIGHTS_ENTITY_TYPES.map(({ id }) => id);
1306
+ var searchEntityTypeIds2 = QLOO_SEARCH_ENTITY_TYPES.map(({ id }) => id);
1307
+ var runtimeInfoByTool = /* @__PURE__ */ new WeakMap();
1308
+ function associateRuntimeInfo(tools, executor) {
1309
+ const info = executor ? {
1310
+ transport: executor.transport.kind,
1311
+ transportName: executor.transport.name,
1312
+ remote: executor.transport.remote ?? false,
1313
+ fallback: "none",
1314
+ ...executor.resolution ? { resolution: executor.resolution } : {}
1315
+ } : {
1316
+ transport: "none",
1317
+ transportName: "Qloo API unavailable",
1318
+ remote: false,
1319
+ fallback: "none"
1320
+ };
1321
+ for (const tool of tools)
1322
+ runtimeInfoByTool.set(tool, info);
1323
+ return [...tools];
1324
+ }
1325
+ function getQlooToolRuntimeInfo(tools) {
1326
+ for (const tool of tools) {
1327
+ const info = runtimeInfoByTool.get(tool);
1328
+ if (info)
1329
+ return info;
1330
+ }
1331
+ return void 0;
1332
+ }
1333
+ function requestOptions(signal, correlationId) {
1334
+ return {
1335
+ ...signal ? { signal } : {},
1336
+ correlationId
1337
+ };
1338
+ }
1339
+ function asRecord(value) {
1340
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1341
+ }
1342
+ function asArray(value) {
1343
+ return Array.isArray(value) ? value : [];
1344
+ }
1345
+ function resultValue(response, key) {
1346
+ const results = response.results;
1347
+ if (Array.isArray(results))
1348
+ return results;
1349
+ return asRecord(results)?.[key] ?? results ?? [];
1350
+ }
1351
+ function resultArray(response, key) {
1352
+ return asArray(resultValue(response, key));
1353
+ }
1354
+ function scalar(record, ...keys) {
1355
+ for (const key of keys) {
1356
+ if (record[key] !== void 0 && record[key] !== null)
1357
+ return record[key];
1358
+ }
1359
+ return void 0;
1360
+ }
1361
+ function boundedValue(value, depth = 0) {
1362
+ if (typeof value === "string")
1363
+ return value.length > 2e3 ? `${value.slice(0, 2e3)}\u2026` : value;
1364
+ if (typeof value === "number" || typeof value === "boolean" || value === null)
1365
+ return value;
1366
+ if (depth >= 5)
1367
+ return "[detail omitted]";
1368
+ if (Array.isArray(value))
1369
+ return value.slice(0, MAX_TOOL_RESULTS).map((item) => boundedValue(item, depth + 1));
1370
+ const record = asRecord(value);
1371
+ if (!record)
1372
+ return void 0;
1373
+ return Object.fromEntries(Object.entries(record).slice(0, 40).map(([key, item]) => [key, boundedValue(item, depth + 1)]));
1374
+ }
1375
+ function compactEntity(value) {
1376
+ const entity = asRecord(value);
1377
+ if (!entity)
1378
+ return void 0;
1379
+ const properties = asRecord(entity.properties);
1380
+ const query = asRecord(entity.query);
1381
+ const explainability = boundedValue(query?.explainability);
1382
+ const compactProperties = properties ? Object.fromEntries([
1383
+ "description",
1384
+ "short_description",
1385
+ "release_year",
1386
+ "release_date",
1387
+ "content_rating",
1388
+ "duration",
1389
+ "image",
1390
+ "geocode",
1391
+ "address",
1392
+ "price_level",
1393
+ "business_rating"
1394
+ ].flatMap((key) => properties[key] === void 0 ? [] : [[key, boundedValue(properties[key])]])) : void 0;
1395
+ return {
1396
+ entity_id: scalar(entity, "entity_id", "id"),
1397
+ name: entity.name,
1398
+ type: entity.type,
1399
+ subtype: entity.subtype,
1400
+ popularity: entity.popularity,
1401
+ affinity: scalar(entity, "affinity") ?? query?.affinity,
1402
+ ...explainability !== void 0 ? { explainability } : {},
1403
+ ...compactProperties && Object.keys(compactProperties).length > 0 ? { properties: compactProperties } : {}
1404
+ };
1405
+ }
1406
+ function compactTag(value) {
1407
+ const tag = asRecord(value);
1408
+ if (!tag)
1409
+ return void 0;
1410
+ const query = asRecord(tag.query);
1411
+ return {
1412
+ id: scalar(tag, "id", "tag_id"),
1413
+ name: tag.name,
1414
+ type: scalar(tag, "type", "subtype"),
1415
+ popularity: tag.popularity,
1416
+ affinity: tag.affinity ?? query?.affinity
1417
+ };
1418
+ }
1419
+ function isCalendarDate(value) {
1420
+ if (!DATE_PATTERN.test(value))
1421
+ return false;
1422
+ const [year, month, day] = value.split("-").map(Number);
1423
+ if (year === void 0 || month === void 0 || day === void 0)
1424
+ return false;
1425
+ if (month < 1 || month > 12 || day < 1)
1426
+ return false;
1427
+ const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
1428
+ const daysInMonth = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1429
+ return day <= (daysInMonth[month - 1] ?? 0);
1430
+ }
1431
+ function heatmapAffinity(value) {
1432
+ const query = asRecord(asRecord(value)?.query);
1433
+ return typeof query?.affinity === "number" ? query.affinity : Number.NEGATIVE_INFINITY;
1434
+ }
1435
+ function interpretedEntity(entity) {
1436
+ return {
1437
+ input: entity.input,
1438
+ entityId: entity.entityId,
1439
+ name: entity.name,
1440
+ ...entity.type ? { type: entity.type } : {},
1441
+ match: entity.match,
1442
+ ...entity.score !== void 0 ? { score: entity.score } : {}
1443
+ };
1444
+ }
1445
+ function interpretedEntities(entities) {
1446
+ return entities.map(interpretedEntity);
1447
+ }
1448
+ async function resolveEntities(provider, inputs, type, signal, correlationId) {
1449
+ const { outcomes } = await provider.resolveEntities({
1450
+ inputs,
1451
+ ...type ? { type } : {}
1452
+ }, {
1453
+ ...signal ? { signal } : {},
1454
+ correlationId
1455
+ });
1456
+ return {
1457
+ resolved: outcomes.flatMap((outcome) => outcome.status === "resolved" ? [{
1458
+ input: outcome.input,
1459
+ entityId: outcome.selected.id,
1460
+ name: outcome.selected.name,
1461
+ ...outcome.selected.type ? { type: outcome.selected.type } : {},
1462
+ match: outcome.match,
1463
+ ...outcome.selected.score !== void 0 ? { score: outcome.selected.score } : {},
1464
+ provenance: outcome.provenance
1465
+ }] : []),
1466
+ issues: outcomes.flatMap((outcome) => outcome.status === "resolved" ? [] : [{
1467
+ input: outcome.input,
1468
+ kind: outcome.status,
1469
+ ...outcome.candidates ? {
1470
+ candidates: outcome.candidates.map((candidate) => ({ ...candidate }))
1471
+ } : {},
1472
+ provenance: outcome.provenance,
1473
+ ...outcome.warnings ? { warnings: outcome.warnings } : {}
1474
+ }]),
1475
+ outcomes
1476
+ };
1477
+ }
1478
+ function interpretedTags(tags) {
1479
+ return tags.map((tag) => ({
1480
+ input: tag.input,
1481
+ tagId: tag.tagId,
1482
+ name: tag.name,
1483
+ match: tag.match,
1484
+ ...tag.score !== void 0 ? { score: tag.score } : {}
1485
+ }));
1486
+ }
1487
+ async function resolveTags(provider, inputs, targetType, purpose, signal, correlationId) {
1488
+ const { outcomes } = await provider.resolveTags({
1489
+ inputs,
1490
+ ...targetType ? { targetType } : {},
1491
+ purpose
1492
+ }, {
1493
+ ...signal ? { signal } : {},
1494
+ correlationId
1495
+ });
1496
+ return {
1497
+ resolved: outcomes.flatMap((outcome) => outcome.status === "resolved" ? [{
1498
+ input: outcome.input,
1499
+ tagId: outcome.selected.id,
1500
+ name: outcome.selected.name,
1501
+ match: outcome.match,
1502
+ ...outcome.selected.score !== void 0 ? { score: outcome.selected.score } : {},
1503
+ ...outcome.alternatives ? {
1504
+ alternatives: outcome.alternatives.map((candidate) => ({ ...candidate }))
1505
+ } : {},
1506
+ provenance: outcome.provenance,
1507
+ ...outcome.warnings ? { warnings: outcome.warnings } : {}
1508
+ }] : []),
1509
+ issues: outcomes.flatMap((outcome) => outcome.status === "resolved" ? [] : [{
1510
+ input: outcome.input,
1511
+ kind: outcome.status,
1512
+ ...outcome.candidates ? {
1513
+ candidates: outcome.candidates.map((candidate) => ({ ...candidate }))
1514
+ } : {},
1515
+ provenance: outcome.provenance,
1516
+ ...outcome.warnings ? { warnings: outcome.warnings } : {}
1517
+ }]),
1518
+ outcomes
1519
+ };
1520
+ }
1521
+ function resolutionWarnings(...sets) {
1522
+ return [...new Set(sets.flatMap(({ outcomes }) => outcomes.flatMap(({ warnings }) => warnings ?? [])))];
1523
+ }
1524
+ function resolutionReport(provider, ...sets) {
1525
+ const warnings = resolutionWarnings(...sets);
1526
+ return {
1527
+ contract_version: provider.descriptor.contract_version,
1528
+ provider: provider.descriptor,
1529
+ outcomes: sets.flatMap(({ outcomes }) => outcomes),
1530
+ ...warnings.length > 0 ? { warnings } : {}
1531
+ };
1532
+ }
1533
+ function resolutionOutput(provider, ...sets) {
1534
+ const warnings = resolutionWarnings(...sets);
1535
+ return {
1536
+ resolution: resolutionReport(provider, ...sets),
1537
+ ...warnings.length > 0 ? { warnings } : {}
1538
+ };
1539
+ }
1540
+ function resolveInsightsType(value) {
1541
+ const resolved = resolveQlooInsightsEntityType(value);
1542
+ if (!resolved) {
1543
+ throw new Error(`Unsupported Qloo Insights type "${value}". Choose: ${insightsEntityTypeIds2.join(", ")}.`);
1544
+ }
1545
+ return resolved;
1546
+ }
1547
+ function resolveSearchType(value) {
1548
+ const resolved = resolveQlooSearchEntityType(value);
1549
+ if (!resolved) {
1550
+ throw new Error(`Unsupported Qloo search type "${value}". Choose: ${searchEntityTypeIds2.join(", ")}.`);
1551
+ }
1552
+ return resolved;
1553
+ }
1554
+ function requireClient(client) {
1555
+ if (!client) {
1556
+ throw new Error("Live Qloo tools require Qloo authentication. Run `qloo setup` for guided configuration.");
1557
+ }
1558
+ return client;
1559
+ }
1560
+ function requireResolutionProvider(provider) {
1561
+ if (!provider) {
1562
+ throw new Error("Live Qloo tools require an explicit resolution provider.");
1563
+ }
1564
+ return provider;
1565
+ }
1566
+ function output(operation, payload) {
1567
+ const result = {
1568
+ schema_version: QLOO_TOOL_RESULT_SCHEMA_VERSION,
1569
+ operation,
1570
+ ...payload
1571
+ };
1572
+ return {
1573
+ content: [{ type: "text", text: JSON.stringify(result) }],
1574
+ details: result
1575
+ };
1576
+ }
1577
+ function needsInput(operation, issues, message) {
1578
+ return output(operation, {
1579
+ status: "needs_input",
1580
+ summary: message,
1581
+ resolution: { issues },
1582
+ results: []
1583
+ });
1584
+ }
1585
+ function provenance(endpoint, docs, queries) {
1586
+ return {
1587
+ source: "Qloo API",
1588
+ endpoint,
1589
+ documentation: `https://docs.qloo.com/reference/${docs}`,
1590
+ requests: queries.slice(0, 20).map((query) => ({
1591
+ method: "GET",
1592
+ path: endpoint,
1593
+ query: boundedValue(query)
1594
+ }))
1595
+ };
1596
+ }
1597
+ function makeCapabilitiesTool(executor) {
1598
+ return defineTool({
1599
+ name: QLOO_CAPABILITIES_TOOL_NAME,
1600
+ label: "Qloo capabilities",
1601
+ description: "Inspect the local, versioned Qloo workflow catalog and pinned API source. Makes no network request.",
1602
+ promptSnippet: "Inspect supported Qloo workflows and authoritative contract metadata",
1603
+ parameters: Type3.Object({
1604
+ operation_ids: Type3.Optional(Type3.Array(Type3.String({
1605
+ enum: QLOO_WORKFLOW_OPERATION_IDS
1606
+ }), { uniqueItems: true }))
1607
+ }, { additionalProperties: false }),
1608
+ async execute(_toolCallId, params) {
1609
+ const operationIds2 = params.operation_ids?.filter(isQlooWorkflowOperationId);
1610
+ return output("capabilities", {
1611
+ manifest_schema_version: QLOO_ADAPTER_CONTRACT_MANIFEST_SCHEMA_VERSION,
1612
+ ...getQlooExecutableCapabilities(operationIds2 === void 0 ? {} : { operation_ids: operationIds2 }),
1613
+ adapter: {
1614
+ id: "qloo_harness",
1615
+ compatibility: "native",
1616
+ canonical_workflow_execution: Boolean(executor),
1617
+ supported_operation_ids: executor ? QLOO_WORKFLOW_OPERATION_IDS : [],
1618
+ capabilities_tool: QLOO_CAPABILITIES_TOOL_NAME
1619
+ },
1620
+ execution_policy: executor ? {
1621
+ active_transport: executor.transport.kind,
1622
+ transport_name: executor.transport.name,
1623
+ remote: executor.transport.remote ?? false,
1624
+ fallback: "none",
1625
+ ...executor.resolution ? { resolution: executor.resolution } : {}
1626
+ } : {
1627
+ active_transport: "none",
1628
+ fallback: "none",
1629
+ reason: "Qloo API authentication is unavailable."
1630
+ }
1631
+ });
1632
+ }
1633
+ });
1634
+ }
1635
+ function makeRecommendTool(clientOption, resolutionProviderOption) {
1636
+ const contract = QLOO_WORKFLOW_CONTRACTS.recommend;
1637
+ return defineTool({
1638
+ name: contract.toolName,
1639
+ label: contract.label,
1640
+ description: contract.description,
1641
+ promptSnippet: contract.promptSnippet,
1642
+ promptGuidelines: [...contract.promptGuidelines],
1643
+ parameters: contract.inputSchema,
1644
+ async execute(toolCallId, params, signal) {
1645
+ const client = requireClient(clientOption);
1646
+ const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
1647
+ const targetType = resolveInsightsType(params.target_type);
1648
+ const demographic = params.demographic ? parseQlooDemographic(params.demographic) : void 0;
1649
+ if (params.demographic && !demographic?.gender && !demographic?.age) {
1650
+ return needsInput("recommend", [], `Could not map demographic "${params.demographic}" to Qloo age or gender signals. Use a phrase such as "young women" or "men 25-34".`);
1651
+ }
1652
+ if (demographic && !params.signal_location) {
1653
+ return needsInput("recommend", [], "Demographic Qloo signals require signal_location so the audience geography is explicit.");
1654
+ }
1655
+ if ((params.signals?.length ?? 0) === 0 && (params.signal_tags?.length ?? 0) === 0 && !params.signal_location && !demographic) {
1656
+ return needsInput("recommend", [], "Provide at least one entity, tag, location, or demographic taste signal.");
1657
+ }
1658
+ const [entities, signalTags, includeTags, excludeTags] = await Promise.all([
1659
+ resolveEntities(resolutionProvider, params.signals ?? [], void 0, signal, toolCallId),
1660
+ resolveTags(resolutionProvider, params.signal_tags ?? [], targetType, "signal", signal, toolCallId),
1661
+ resolveTags(resolutionProvider, params.include_tags ?? [], targetType, "include_filter", signal, toolCallId),
1662
+ resolveTags(resolutionProvider, params.exclude_tags ?? [], targetType, "exclude_filter", signal, toolCallId)
1663
+ ]);
1664
+ const issues = [...entities.issues, ...signalTags.issues, ...includeTags.issues, ...excludeTags.issues];
1665
+ if (issues.length > 0)
1666
+ return needsInput("recommend", issues, "Choose or correct the unresolved Qloo inputs.");
1667
+ const query = {
1668
+ "filter.type": targetType,
1669
+ take: params.limit ?? DEFAULT_LIMIT
1670
+ };
1671
+ if (entities.resolved.length > 0)
1672
+ query["signal.interests.entities"] = entities.resolved.map(({ entityId }) => entityId);
1673
+ if (signalTags.resolved.length > 0) {
1674
+ query["signal.interests.tags"] = signalTags.resolved.map(({ tagId }) => tagId);
1675
+ query["operator.signal.interests.tags"] = params.signal_tags_operator ?? "intersection";
1676
+ }
1677
+ if (params.signal_location)
1678
+ query["signal.location.query"] = params.signal_location;
1679
+ if (demographic?.gender)
1680
+ query["signal.demographics.gender"] = demographic.gender;
1681
+ if (demographic?.age)
1682
+ query["signal.demographics.age"] = demographic.age;
1683
+ if (params.filter_location)
1684
+ query["filter.location.query"] = params.filter_location;
1685
+ if (includeTags.resolved.length > 0) {
1686
+ query["filter.tags"] = includeTags.resolved.map(({ tagId }) => tagId);
1687
+ query["operator.filter.tags"] = params.include_tags_operator ?? "union";
1688
+ }
1689
+ if (excludeTags.resolved.length > 0) {
1690
+ query["filter.exclude.tags"] = excludeTags.resolved.map(({ tagId }) => tagId);
1691
+ query["operator.filter.exclude.tags"] = params.exclude_tags_operator ?? "union";
1692
+ }
1693
+ if (params.explain ?? true)
1694
+ query["feature.explainability"] = true;
1695
+ const response = await client.insights(query, requestOptions(signal, toolCallId));
1696
+ const results = resultArray(response, "entities").map(compactEntity).filter((entity) => entity !== void 0).slice(0, params.limit ?? DEFAULT_LIMIT);
1697
+ const explainability = boundedValue(asRecord(response.query)?.explainability);
1698
+ return output("recommend", {
1699
+ status: "ok",
1700
+ interpretation: {
1701
+ target_type: targetType,
1702
+ signals: interpretedEntities(entities.resolved),
1703
+ signal_tags: interpretedTags(signalTags.resolved),
1704
+ signal_tags_operator: signalTags.resolved.length > 0 ? params.signal_tags_operator ?? "intersection" : void 0,
1705
+ signal_location: params.signal_location,
1706
+ demographic: params.demographic ? { input: params.demographic, ...demographic } : void 0,
1707
+ filter_location: params.filter_location,
1708
+ include_tags: interpretedTags(includeTags.resolved),
1709
+ include_tags_operator: includeTags.resolved.length > 0 ? params.include_tags_operator ?? "union" : void 0,
1710
+ exclude_tags: interpretedTags(excludeTags.resolved),
1711
+ exclude_tags_operator: excludeTags.resolved.length > 0 ? params.exclude_tags_operator ?? "union" : void 0
1712
+ },
1713
+ ...resolutionOutput(resolutionProvider, entities, signalTags, includeTags, excludeTags),
1714
+ results,
1715
+ result_count: results.length,
1716
+ ...explainability !== void 0 ? { explainability } : {},
1717
+ provenance: provenance("/v2/insights", "insights-api-deep-dive", [query])
1718
+ });
1719
+ }
1720
+ });
1721
+ }
1722
+ function makeRankTool(clientOption, resolutionProviderOption) {
1723
+ const contract = QLOO_WORKFLOW_CONTRACTS.rank;
1724
+ return defineTool({
1725
+ name: contract.toolName,
1726
+ label: contract.label,
1727
+ description: contract.description,
1728
+ promptSnippet: contract.promptSnippet,
1729
+ promptGuidelines: [...contract.promptGuidelines],
1730
+ parameters: contract.inputSchema,
1731
+ async execute(toolCallId, params, signal) {
1732
+ const client = requireClient(clientOption);
1733
+ const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
1734
+ const targetType = resolveInsightsType(params.option_type);
1735
+ const demographic = params.demographic ? parseQlooDemographic(params.demographic) : void 0;
1736
+ if (params.demographic && !demographic?.gender && !demographic?.age) {
1737
+ return needsInput("rank", [], `Could not map demographic "${params.demographic}" to Qloo age or gender signals.`);
1738
+ }
1739
+ if (demographic && !params.signal_location) {
1740
+ return needsInput("rank", [], "Demographic Qloo signals require signal_location so the audience geography is explicit.");
1741
+ }
1742
+ if ((params.signals?.length ?? 0) === 0 && !params.signal_location && !demographic) {
1743
+ return needsInput("rank", [], "Ranking requires an entity, location, or demographic signal.");
1744
+ }
1745
+ const [options, signals, includeTags, excludeTags] = await Promise.all([
1746
+ resolveEntities(resolutionProvider, params.options, targetType, signal, toolCallId),
1747
+ resolveEntities(resolutionProvider, params.signals ?? [], void 0, signal, toolCallId),
1748
+ resolveTags(resolutionProvider, params.include_tags ?? [], targetType, "include_filter", signal, toolCallId),
1749
+ resolveTags(resolutionProvider, params.exclude_tags ?? [], targetType, "exclude_filter", signal, toolCallId)
1750
+ ]);
1751
+ const issues = [...options.issues, ...signals.issues, ...includeTags.issues, ...excludeTags.issues];
1752
+ if (issues.length > 0)
1753
+ return needsInput("rank", issues, "Choose or correct the unresolved ranking inputs.");
1754
+ const query = {
1755
+ "filter.type": targetType,
1756
+ "filter.results.entities": options.resolved.map(({ entityId }) => entityId),
1757
+ take: options.resolved.length
1758
+ };
1759
+ if (signals.resolved.length > 0)
1760
+ query["signal.interests.entities"] = signals.resolved.map(({ entityId }) => entityId);
1761
+ if (params.signal_location)
1762
+ query["signal.location.query"] = params.signal_location;
1763
+ if (demographic?.gender)
1764
+ query["signal.demographics.gender"] = demographic.gender;
1765
+ if (demographic?.age)
1766
+ query["signal.demographics.age"] = demographic.age;
1767
+ if (includeTags.resolved.length > 0) {
1768
+ query["filter.tags"] = includeTags.resolved.map(({ tagId }) => tagId);
1769
+ query["operator.filter.tags"] = "union";
1770
+ }
1771
+ if (excludeTags.resolved.length > 0) {
1772
+ query["filter.exclude.tags"] = excludeTags.resolved.map(({ tagId }) => tagId);
1773
+ query["operator.filter.exclude.tags"] = "union";
1774
+ }
1775
+ const response = await client.insights(query, requestOptions(signal, toolCallId));
1776
+ const results = resultArray(response, "entities").map(compactEntity).filter((entity) => entity !== void 0).slice(0, options.resolved.length);
1777
+ return output("rank", {
1778
+ status: "ok",
1779
+ interpretation: {
1780
+ option_type: targetType,
1781
+ options: interpretedEntities(options.resolved),
1782
+ signals: interpretedEntities(signals.resolved),
1783
+ signal_location: params.signal_location,
1784
+ demographic: params.demographic ? { input: params.demographic, ...demographic } : void 0,
1785
+ include_tags: interpretedTags(includeTags.resolved),
1786
+ exclude_tags: interpretedTags(excludeTags.resolved)
1787
+ },
1788
+ ...resolutionOutput(resolutionProvider, options, signals, includeTags, excludeTags),
1789
+ results,
1790
+ result_count: results.length,
1791
+ provenance: provenance("/v2/insights", "insights-api-deep-dive", [query])
1792
+ });
1793
+ }
1794
+ });
1795
+ }
1796
+ function makeDescribeTool(clientOption, resolutionProviderOption) {
1797
+ const contract = QLOO_WORKFLOW_CONTRACTS.describe;
1798
+ return defineTool({
1799
+ name: contract.toolName,
1800
+ label: contract.label,
1801
+ description: contract.description,
1802
+ promptSnippet: contract.promptSnippet,
1803
+ parameters: contract.inputSchema,
1804
+ async execute(toolCallId, params, signal) {
1805
+ const client = requireClient(clientOption);
1806
+ const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
1807
+ const type = params.type ? resolveSearchType(params.type) : void 0;
1808
+ const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId);
1809
+ if (resolution.issues.length > 0)
1810
+ return needsInput("describe", resolution.issues, "Choose the intended Qloo entity.");
1811
+ const needsDetailHydration = resolution.resolved.some(({ match }) => match !== "identifier");
1812
+ const detailedResults = needsDetailHydration ? resultArray(await client.entities(resolution.resolved.map(({ entityId }) => entityId), requestOptions(signal, toolCallId)), "entities").map(compactEntity).filter((entity) => entity !== void 0) : [];
1813
+ const results = detailedResults.length > 0 ? detailedResults : resolution.resolved.map((entity) => ({
1814
+ entity_id: entity.entityId,
1815
+ name: entity.name,
1816
+ type: entity.type
1817
+ }));
1818
+ return output("describe", {
1819
+ status: results.length > 0 ? "ok" : "empty",
1820
+ interpretation: {
1821
+ entity: resolution.resolved[0] ? interpretedEntity(resolution.resolved[0]) : void 0
1822
+ },
1823
+ ...resolutionOutput(resolutionProvider, resolution),
1824
+ results,
1825
+ result_count: results.length,
1826
+ provenance: provenance("/entities", "get-entities", [{
1827
+ entity_ids: resolution.resolved.map(({ entityId }) => entityId)
1828
+ }])
1829
+ });
1830
+ }
1831
+ });
1832
+ }
1833
+ function makeWherePopularTool(clientOption, resolutionProviderOption) {
1834
+ const contract = QLOO_WORKFLOW_CONTRACTS.where_popular;
1835
+ return defineTool({
1836
+ name: contract.toolName,
1837
+ label: contract.label,
1838
+ description: contract.description,
1839
+ promptSnippet: contract.promptSnippet,
1840
+ parameters: contract.inputSchema,
1841
+ async execute(toolCallId, params, signal) {
1842
+ const client = requireClient(clientOption);
1843
+ const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
1844
+ const type = params.entity_type ? resolveSearchType(params.entity_type) : void 0;
1845
+ const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId);
1846
+ if (resolution.issues.length > 0)
1847
+ return needsInput("where_popular", resolution.issues, "Choose the intended Qloo entity.");
1848
+ const entity = resolution.resolved[0];
1849
+ if (!entity)
1850
+ return needsInput("where_popular", [], "Provide an entity.");
1851
+ const limit = params.limit ?? DEFAULT_LIMIT;
1852
+ const query = {
1853
+ "filter.type": "urn:heatmap",
1854
+ "signal.interests.entities": entity.entityId,
1855
+ "filter.location.query": params.within,
1856
+ take: limit
1857
+ };
1858
+ const response = await client.insights(query, requestOptions(signal, toolCallId));
1859
+ const results = [...resultArray(response, "heatmap")].sort((left, right) => heatmapAffinity(right) - heatmapAffinity(left)).slice(0, limit).map((point) => boundedValue(point));
1860
+ return output("where_popular", {
1861
+ status: results.length > 0 ? "ok" : "empty",
1862
+ interpretation: { entity: interpretedEntity(entity), within: params.within },
1863
+ ...resolutionOutput(resolutionProvider, resolution),
1864
+ results,
1865
+ result_count: results.length,
1866
+ provenance: provenance("/v2/insights", "heatmaps-use-case", [query])
1867
+ });
1868
+ }
1869
+ });
1870
+ }
1871
+ function makeCompareAudiencesTool(clientOption, resolutionProviderOption) {
1872
+ const contract = QLOO_WORKFLOW_CONTRACTS.compare_audiences;
1873
+ return defineTool({
1874
+ name: contract.toolName,
1875
+ label: contract.label,
1876
+ description: contract.description,
1877
+ promptSnippet: contract.promptSnippet,
1878
+ parameters: contract.inputSchema,
1879
+ async execute(toolCallId, params, signal) {
1880
+ const client = requireClient(clientOption);
1881
+ const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
1882
+ const [groupA, groupB] = await Promise.all([
1883
+ resolveEntities(resolutionProvider, params.group_a, void 0, signal, toolCallId),
1884
+ resolveEntities(resolutionProvider, params.group_b, void 0, signal, toolCallId)
1885
+ ]);
1886
+ const issues = [...groupA.issues, ...groupB.issues];
1887
+ if (issues.length > 0)
1888
+ return needsInput("compare_audiences", issues, "Choose or correct the unresolved comparison inputs.");
1889
+ const query = {
1890
+ "a.signal.interests.entities": groupA.resolved.map(({ entityId }) => entityId),
1891
+ "b.signal.interests.entities": groupB.resolved.map(({ entityId }) => entityId),
1892
+ take: params.limit ?? DEFAULT_LIMIT
1893
+ };
1894
+ if (params.target_type)
1895
+ query["filter.type"] = resolveInsightsType(params.target_type);
1896
+ const response = await client.compare(query, requestOptions(signal, toolCallId));
1897
+ return output("compare_audiences", {
1898
+ status: "ok",
1899
+ interpretation: {
1900
+ group_a: interpretedEntities(groupA.resolved),
1901
+ group_b: interpretedEntities(groupB.resolved),
1902
+ target_type: query["filter.type"]
1903
+ },
1904
+ ...resolutionOutput(resolutionProvider, groupA, groupB),
1905
+ results: boundedValue(response.results),
1906
+ provenance: provenance("/v2/analysis/compare", "analysis-compare", [query])
1907
+ });
1908
+ }
1909
+ });
1910
+ }
1911
+ function makeEntityTagsTool(clientOption, resolutionProviderOption) {
1912
+ const contract = QLOO_WORKFLOW_CONTRACTS.entity_tags;
1913
+ return defineTool({
1914
+ name: contract.toolName,
1915
+ label: contract.label,
1916
+ description: contract.description,
1917
+ promptSnippet: contract.promptSnippet,
1918
+ parameters: contract.inputSchema,
1919
+ async execute(toolCallId, params, signal) {
1920
+ const client = requireClient(clientOption);
1921
+ const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
1922
+ const type = params.entity_type ? resolveSearchType(params.entity_type) : void 0;
1923
+ const resolution = await resolveEntities(resolutionProvider, params.entities, type, signal, toolCallId);
1924
+ if (resolution.issues.length > 0)
1925
+ return needsInput("entity_tags", resolution.issues, "Choose or correct the unresolved Qloo entities.");
1926
+ const query = {
1927
+ "filter.type": "urn:tag",
1928
+ "signal.interests.entities": resolution.resolved.map(({ entityId }) => entityId),
1929
+ take: params.limit ?? DEFAULT_LIMIT
1930
+ };
1931
+ const response = await client.insights(query, requestOptions(signal, toolCallId));
1932
+ const results = resultArray(response, "tags").map(compactTag).filter((tag) => tag !== void 0).slice(0, params.limit ?? DEFAULT_LIMIT);
1933
+ return output("entity_tags", {
1934
+ status: "ok",
1935
+ interpretation: { entities: interpretedEntities(resolution.resolved) },
1936
+ ...resolutionOutput(resolutionProvider, resolution),
1937
+ results,
1938
+ result_count: results.length,
1939
+ provenance: provenance("/v2/insights", "taste-analysis", [query])
1940
+ });
1941
+ }
1942
+ });
1943
+ }
1944
+ function makeAudienceDemographicsTool(clientOption, resolutionProviderOption) {
1945
+ const contract = QLOO_WORKFLOW_CONTRACTS.audience_demographics;
1946
+ return defineTool({
1947
+ name: contract.toolName,
1948
+ label: contract.label,
1949
+ description: contract.description,
1950
+ promptSnippet: contract.promptSnippet,
1951
+ parameters: contract.inputSchema,
1952
+ async execute(toolCallId, params, signal) {
1953
+ const client = requireClient(clientOption);
1954
+ const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
1955
+ const type = params.entity_type ? resolveSearchType(params.entity_type) : void 0;
1956
+ const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId);
1957
+ if (resolution.issues.length > 0)
1958
+ return needsInput("audience_demographics", resolution.issues, "Choose the intended Qloo entity.");
1959
+ const entity = resolution.resolved[0];
1960
+ if (!entity)
1961
+ return needsInput("audience_demographics", [], "Provide an entity.");
1962
+ const query = {
1963
+ "filter.type": "urn:demographics",
1964
+ "signal.interests.entities": entity.entityId,
1965
+ take: params.limit ?? DEFAULT_LIMIT
1966
+ };
1967
+ const response = await client.insights(query, requestOptions(signal, toolCallId));
1968
+ const results = resultArray(response, "demographics").slice(0, params.limit ?? DEFAULT_LIMIT).map((item) => boundedValue(item));
1969
+ return output("audience_demographics", {
1970
+ status: "ok",
1971
+ interpretation: { entity: interpretedEntity(entity) },
1972
+ ...resolutionOutput(resolutionProvider, resolution),
1973
+ results,
1974
+ result_count: results.length,
1975
+ provenance: provenance("/v2/insights", "demographics-use-case", [query])
1976
+ });
1977
+ }
1978
+ });
1979
+ }
1980
+ function makeTrendsTool(clientOption, resolutionProviderOption) {
1981
+ const contract = QLOO_WORKFLOW_CONTRACTS.trends;
1982
+ return defineTool({
1983
+ name: contract.toolName,
1984
+ label: contract.label,
1985
+ description: contract.description,
1986
+ promptSnippet: contract.promptSnippet,
1987
+ parameters: contract.inputSchema,
1988
+ async execute(toolCallId, params, signal) {
1989
+ const client = requireClient(clientOption);
1990
+ const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
1991
+ if (!isCalendarDate(params.start_date) || !isCalendarDate(params.end_date) || params.start_date > params.end_date) {
1992
+ throw new Error("Trend dates must be valid YYYY-MM-DD values with start_date on or before end_date.");
1993
+ }
1994
+ const type = resolveInsightsType(params.entity_type);
1995
+ const resolution = await resolveEntities(resolutionProvider, params.entities, type, signal, toolCallId);
1996
+ if (resolution.issues.length > 0)
1997
+ return needsInput("trends", resolution.issues, "Choose or correct the unresolved Qloo entities.");
1998
+ const requests = resolution.resolved.map((entity) => ({
1999
+ entity,
2000
+ query: {
2001
+ "filter.type": type,
2002
+ "filter.start_date": params.start_date,
2003
+ "filter.end_date": params.end_date,
2004
+ "signal.interests.entities": entity.entityId,
2005
+ take: params.limit ?? DEFAULT_LIMIT
2006
+ }
2007
+ }));
2008
+ const series = await Promise.all(requests.map(async ({ entity, query }) => {
2009
+ const response = await client.trending(query, requestOptions(signal, toolCallId));
2010
+ const points = resultArray(response, "trending").slice(0, params.limit ?? DEFAULT_LIMIT).map((point) => boundedValue(point));
2011
+ return { entity: interpretedEntity(entity), points };
2012
+ }));
2013
+ return output("trends", {
2014
+ status: "ok",
2015
+ interpretation: { entity_type: type, start_date: params.start_date, end_date: params.end_date },
2016
+ ...resolutionOutput(resolutionProvider, resolution),
2017
+ series,
2018
+ result_count: series.length,
2019
+ provenance: provenance("/v2/trending", "get-trending", requests.map(({ query }) => query))
2020
+ });
2021
+ }
2022
+ });
2023
+ }
2024
+ function makeFindTagsTool(clientOption) {
2025
+ const contract = QLOO_WORKFLOW_CONTRACTS.find_tags;
2026
+ return defineTool({
2027
+ name: contract.toolName,
2028
+ label: contract.label,
2029
+ description: contract.description,
2030
+ promptSnippet: contract.promptSnippet,
2031
+ parameters: contract.inputSchema,
2032
+ async execute(toolCallId, params, signal) {
2033
+ const client = requireClient(clientOption);
2034
+ const query = {
2035
+ "filter.query": params.query,
2036
+ "feature.semantic_search": params.semantic ?? true,
2037
+ take: params.limit ?? DEFAULT_LIMIT
2038
+ };
2039
+ const response = await client.tags(query, requestOptions(signal, toolCallId));
2040
+ const results = resultArray(response, "tags").map(compactTag).filter((tag) => tag !== void 0).slice(0, params.limit ?? DEFAULT_LIMIT);
2041
+ return output("find_tags", {
2042
+ status: "ok",
2043
+ interpretation: { query: params.query, semantic: params.semantic ?? true },
2044
+ results,
2045
+ result_count: results.length,
2046
+ provenance: provenance("/v2/tags", "get-tags", [query])
2047
+ });
2048
+ }
2049
+ });
2050
+ }
2051
+ function createDirectOperationTools(client, resolutionProvider) {
2052
+ return [
2053
+ makeRecommendTool(client, resolutionProvider),
2054
+ makeRankTool(client, resolutionProvider),
2055
+ makeDescribeTool(client, resolutionProvider),
2056
+ makeWherePopularTool(client, resolutionProvider),
2057
+ makeCompareAudiencesTool(client, resolutionProvider),
2058
+ makeEntityTagsTool(client, resolutionProvider),
2059
+ makeAudienceDemographicsTool(client, resolutionProvider),
2060
+ makeTrendsTool(client, resolutionProvider),
2061
+ makeFindTagsTool(client)
2062
+ ];
2063
+ }
2064
+ function createDirectQlooWorkflowExecutor(client, observer, resolutionProvider = createPublicQlooResolutionProvider(client)) {
2065
+ const tools = new Map(createDirectOperationTools(client, resolutionProvider).map((tool) => [tool.name, tool]));
2066
+ return createDelegatingQlooWorkflowExecutor({
2067
+ transport: { kind: "direct", name: "Qloo API" },
2068
+ resolution: resolutionProvider.descriptor,
2069
+ ...observer ? { observer } : {},
2070
+ async invoke(operation, input, context) {
2071
+ const toolName = `qloo_${operation}`;
2072
+ const tool = tools.get(toolName);
2073
+ if (!tool)
2074
+ throw new Error(`Direct Qloo executor does not implement ${toolName}.`);
2075
+ const result = await tool.execute(context.correlationId ?? `direct-${operation}`, input, context.signal, void 0, {});
2076
+ const details = result.details;
2077
+ if (details === null || typeof details !== "object" || Array.isArray(details)) {
2078
+ throw new Error(`${toolName} returned an invalid result envelope.`);
2079
+ }
2080
+ return details;
2081
+ }
2082
+ });
2083
+ }
2084
+ function makeExecutorTool(operation, executor) {
2085
+ const contract = QLOO_WORKFLOW_CONTRACTS[operation];
2086
+ const promptGuidelines = "promptGuidelines" in contract && Array.isArray(contract.promptGuidelines) ? contract.promptGuidelines : void 0;
2087
+ return defineTool({
2088
+ name: contract.toolName,
2089
+ label: contract.label,
2090
+ description: contract.description,
2091
+ promptSnippet: contract.promptSnippet,
2092
+ ...promptGuidelines ? { promptGuidelines: [...promptGuidelines] } : {},
2093
+ parameters: contract.inputSchema,
2094
+ renderShell: "self",
2095
+ renderCall: (params, theme) => createQlooCallComponent(contract.toolName, params, theme),
2096
+ renderResult: (result, renderOptions, theme) => createQlooResultComponent(result.details, renderOptions.expanded, theme),
2097
+ async execute(toolCallId, params, signal) {
2098
+ try {
2099
+ const execution = await executor.execute(operation, params, {
2100
+ ...signal ? { signal } : {},
2101
+ correlationId: toolCallId
2102
+ });
2103
+ return {
2104
+ content: [{ type: "text", text: JSON.stringify(execution.result) }],
2105
+ details: execution.result
2106
+ };
2107
+ } catch (error) {
2108
+ if (!(error instanceof QlooWorkflowExecutionError) || !error.expected)
2109
+ throw error;
2110
+ return output(operation, {
2111
+ status: "error",
2112
+ summary: error.message,
2113
+ query_intent: createQlooQueryIntent(operation, params),
2114
+ error: {
2115
+ code: error.code,
2116
+ layer: error.layer,
2117
+ retryable: error.retryable,
2118
+ recovery: error.recovery
2119
+ },
2120
+ results: [],
2121
+ execution: {
2122
+ transport: error.transport.kind,
2123
+ transport_name: error.transport.name,
2124
+ correlation_id: error.correlationId ?? toolCallId,
2125
+ duration_ms: error.durationMs ?? 0
2126
+ }
2127
+ });
2128
+ }
2129
+ }
2130
+ });
2131
+ }
2132
+ function createQlooTools(options = {}) {
2133
+ if (options.client && options.executor) {
2134
+ throw new Error("Choose either a Qloo client or a workflow executor, not both.");
2135
+ }
2136
+ if (options.executor && options.resolutionProvider) {
2137
+ throw new Error("A custom workflow executor must own its resolution provider.");
2138
+ }
2139
+ const executor = options.executor ?? (options.client ? createDirectQlooWorkflowExecutor(options.client, options.executionObserver, options.resolutionProvider ?? createPublicQlooResolutionProvider(options.client)) : void 0);
2140
+ if (!executor)
2141
+ return associateRuntimeInfo([makeCapabilitiesTool()]);
2142
+ return associateRuntimeInfo([
2143
+ makeCapabilitiesTool(executor),
2144
+ ...QLOO_WORKFLOW_OPERATION_IDS.map((operation) => makeExecutorTool(operation, executor))
2145
+ ], executor);
2146
+ }
2147
+ function createDirectQlooWorkflowExecutorFromEnvironment(options = {}) {
2148
+ const env = options.env ?? process.env;
2149
+ const resolved = resolveQlooClientConfiguration({
2150
+ env,
2151
+ ...options.configPath ? { configPath: options.configPath } : {},
2152
+ ...options.readConfigFile ? { readFile: options.readConfigFile } : {}
2153
+ });
2154
+ if (!resolved.apiKey)
2155
+ return void 0;
2156
+ const executionObserver = options.executionObserver ?? createFileExecutionObserver({
2157
+ logsDirectory: resolveQlooPaths({ env }).logsDir
2158
+ });
2159
+ const client = new QlooClient({
2160
+ apiKey: resolved.apiKey,
2161
+ ...resolved.baseUrl ? { baseUrl: resolved.baseUrl } : {},
2162
+ allowCustomBaseUrl: resolved.baseUrlTrusted
2163
+ });
2164
+ const resolutionProvider = options.resolutionProvider ?? createQlooResolutionProviderFromEnvironment({
2165
+ client,
2166
+ env,
2167
+ fileConfig: resolved.fileConfig
2168
+ });
2169
+ return createDirectQlooWorkflowExecutor(client, executionObserver, resolutionProvider);
2170
+ }
2171
+ function createQlooToolsFromEnvironment(options = {}) {
2172
+ if (options.executor) {
2173
+ if (options.client)
2174
+ throw new Error("Choose either a Qloo client or a workflow executor, not both.");
2175
+ if (options.resolutionProvider)
2176
+ throw new Error("A custom workflow executor must own its resolution provider.");
2177
+ return createQlooTools({ executor: options.executor });
2178
+ }
2179
+ if (options.client) {
2180
+ return createQlooTools({
2181
+ client: options.client,
2182
+ ...options.executionObserver ? { executionObserver: options.executionObserver } : {},
2183
+ ...options.resolutionProvider ? { resolutionProvider: options.resolutionProvider } : {}
2184
+ });
2185
+ }
2186
+ const executor = createDirectQlooWorkflowExecutorFromEnvironment(options);
2187
+ return executor ? createQlooTools({ executor }) : createQlooTools();
2188
+ }
2189
+ export {
2190
+ createDirectQlooWorkflowExecutor,
2191
+ createDirectQlooWorkflowExecutorFromEnvironment,
2192
+ createQlooTools,
2193
+ createQlooToolsFromEnvironment,
2194
+ getQlooToolRuntimeInfo
2195
+ };