@ainyc/canonry 1.26.2 → 1.27.2

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.
@@ -88,13 +88,42 @@ Do not write config.yaml by hand; use "canonry init", "canonry settings", or "ca
88
88
  }
89
89
  return parsed;
90
90
  }
91
+ function loadConfigRaw() {
92
+ const configPath = getConfigPath();
93
+ if (!fs.existsSync(configPath)) return null;
94
+ try {
95
+ return parse(fs.readFileSync(configPath, "utf-8")) ?? null;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
91
100
  function saveConfig(config) {
92
101
  const configDir = getConfigDir();
93
102
  if (!fs.existsSync(configDir)) {
94
103
  fs.mkdirSync(configDir, { recursive: true });
95
104
  }
96
- const yaml = stringify(config);
97
- fs.writeFileSync(getConfigPath(), yaml, { encoding: "utf-8", mode: 384 });
105
+ const configPath = getConfigPath();
106
+ const onDisk = loadConfigRaw();
107
+ const merged = onDisk ? { ...onDisk } : {};
108
+ for (const [key, value] of Object.entries(config)) {
109
+ if (value !== void 0) {
110
+ merged[key] = value;
111
+ }
112
+ }
113
+ if (onDisk) {
114
+ if (process.env.CANONRY_PORT?.trim() || onDisk.basePath) {
115
+ merged.apiUrl = onDisk.apiUrl;
116
+ }
117
+ if ("CANONRY_BASE_PATH" in process.env) {
118
+ if (onDisk.basePath !== void 0) {
119
+ merged.basePath = onDisk.basePath;
120
+ } else {
121
+ delete merged.basePath;
122
+ }
123
+ }
124
+ }
125
+ const yaml = stringify(merged);
126
+ fs.writeFileSync(configPath, yaml, { encoding: "utf-8", mode: 384 });
98
127
  }
99
128
  function configExists() {
100
129
  return fs.existsSync(getConfigPath());
@@ -5051,6 +5080,14 @@ async function settingsRoutes(app, opts) {
5051
5080
  const err = validationError("baseUrl is required for local provider");
5052
5081
  return reply.status(err.statusCode).send(err.toJSON());
5053
5082
  }
5083
+ } else if (name === "gemini" && !apiKey) {
5084
+ const geminiSummary = (opts.providerSummary ?? []).find((p) => p.name === "gemini");
5085
+ if (!geminiSummary?.vertexConfigured) {
5086
+ const err = validationError(
5087
+ "apiKey is required for Gemini unless Vertex AI is configured (set GEMINI_VERTEX_PROJECT env var or vertexProject in config file)"
5088
+ );
5089
+ return reply.status(err.statusCode).send(err.toJSON());
5090
+ }
5054
5091
  } else {
5055
5092
  if (!apiKey || typeof apiKey !== "string") {
5056
5093
  const err = validationError("apiKey is required");
@@ -7631,18 +7668,58 @@ async function apiRoutes(app, opts) {
7631
7668
 
7632
7669
  // ../provider-gemini/src/normalize.ts
7633
7670
  import { GoogleGenerativeAI } from "@google/generative-ai";
7671
+ import { VertexAI } from "@google-cloud/vertexai";
7634
7672
  var DEFAULT_MODEL = "gemini-3-flash";
7635
7673
  var VALIDATION_PATTERN = /^gemini-/;
7674
+ function isVertexConfig(config) {
7675
+ return !!config.vertexProject;
7676
+ }
7636
7677
  function resolveModel(config) {
7637
7678
  const m = config.model;
7638
7679
  if (!m) return DEFAULT_MODEL;
7639
7680
  if (VALIDATION_PATTERN.test(m)) return m;
7681
+ const backend = isVertexConfig(config) ? "Vertex AI" : "AI Studio";
7640
7682
  console.warn(
7641
- `[provider-gemini] Invalid model name "${m}" \u2014 this provider uses the Gemini AI Studio API (generativelanguage.googleapis.com) which only accepts "gemini-*" model names. Falling back to ${DEFAULT_MODEL}.`
7683
+ `[provider-gemini] Invalid model name "${m}" \u2014 this provider uses the Gemini ${backend} API which only accepts "gemini-*" model names. Falling back to ${DEFAULT_MODEL}.`
7642
7684
  );
7643
7685
  return DEFAULT_MODEL;
7644
7686
  }
7687
+ function wrapVertexResponse(response) {
7688
+ return {
7689
+ text: () => {
7690
+ const parts = response.candidates?.[0]?.content?.parts;
7691
+ return parts?.map((p) => p.text ?? "").join("") ?? "";
7692
+ },
7693
+ candidates: response.candidates?.map((c) => ({
7694
+ content: c.content,
7695
+ finishReason: c.finishReason,
7696
+ groundingMetadata: c.groundingMetadata
7697
+ })),
7698
+ usageMetadata: response.usageMetadata
7699
+ };
7700
+ }
7701
+ function createVertexModel(config, model, tools) {
7702
+ const vertexOpts = {
7703
+ project: config.vertexProject,
7704
+ location: config.vertexRegion || "us-central1",
7705
+ googleAuthOptions: config.vertexCredentials ? { keyFilename: config.vertexCredentials } : void 0
7706
+ };
7707
+ const vertexAI = new VertexAI(vertexOpts);
7708
+ return vertexAI.getGenerativeModel({
7709
+ model,
7710
+ ...tools ? { tools } : {}
7711
+ });
7712
+ }
7645
7713
  function validateConfig(config) {
7714
+ if (isVertexConfig(config)) {
7715
+ const model2 = resolveModel(config);
7716
+ return {
7717
+ ok: true,
7718
+ provider: "gemini",
7719
+ message: "config valid (Vertex AI)",
7720
+ model: model2
7721
+ };
7722
+ }
7646
7723
  if (!config.apiKey || config.apiKey.length === 0) {
7647
7724
  return { ok: false, provider: "gemini", message: "missing api key" };
7648
7725
  }
@@ -7659,16 +7736,29 @@ async function healthcheck(config) {
7659
7736
  const validation = validateConfig(config);
7660
7737
  if (!validation.ok) return validation;
7661
7738
  try {
7662
- const genAI = new GoogleGenerativeAI(config.apiKey);
7663
- const model = genAI.getGenerativeModel({ model: resolveModel(config) });
7664
- const result = await model.generateContent('Say "ok"');
7665
- const text2 = result.response.text();
7666
- return {
7667
- ok: text2.length > 0,
7668
- provider: "gemini",
7669
- message: text2.length > 0 ? "gemini api key verified" : "empty response from gemini",
7670
- model: config.model ?? DEFAULT_MODEL
7671
- };
7739
+ const model = resolveModel(config);
7740
+ if (isVertexConfig(config)) {
7741
+ const generativeModel = createVertexModel(config, model);
7742
+ const result = await generativeModel.generateContent('Say "ok"');
7743
+ const text2 = result.response.candidates?.[0]?.content?.parts?.map((p) => p.text ?? "").join("") ?? "";
7744
+ return {
7745
+ ok: text2.length > 0,
7746
+ provider: "gemini",
7747
+ message: text2.length > 0 ? "gemini vertex ai verified" : "empty response from gemini vertex ai",
7748
+ model
7749
+ };
7750
+ } else {
7751
+ const genAI = new GoogleGenerativeAI(config.apiKey);
7752
+ const generativeModel = genAI.getGenerativeModel({ model });
7753
+ const result = await generativeModel.generateContent('Say "ok"');
7754
+ const text2 = result.response.text();
7755
+ return {
7756
+ ok: text2.length > 0,
7757
+ provider: "gemini",
7758
+ message: text2.length > 0 ? "gemini api key verified" : "empty response from gemini",
7759
+ model
7760
+ };
7761
+ }
7672
7762
  } catch (err) {
7673
7763
  return {
7674
7764
  ok: false,
@@ -7680,23 +7770,41 @@ async function healthcheck(config) {
7680
7770
  }
7681
7771
  async function executeTrackedQuery(input) {
7682
7772
  const model = resolveModel(input.config);
7683
- const genAI = new GoogleGenerativeAI(input.config.apiKey);
7684
- const generativeModel = genAI.getGenerativeModel({
7685
- model,
7686
- tools: [{ googleSearch: {} }]
7687
- });
7688
7773
  const prompt = buildPrompt(input.keyword, input.location);
7689
- const result = await generativeModel.generateContent(prompt);
7690
- const response = result.response;
7691
- const groundingMetadata = extractGroundingMetadata(response);
7692
- const searchQueries = extractSearchQueries(response);
7693
- return {
7694
- provider: "gemini",
7695
- rawResponse: responseToRecord(response),
7696
- model,
7697
- groundingSources: groundingMetadata,
7698
- searchQueries
7699
- };
7774
+ if (isVertexConfig(input.config)) {
7775
+ const vertexSearchTool = { googleSearchRetrieval: {} };
7776
+ const generativeModel = createVertexModel(input.config, model, [vertexSearchTool]);
7777
+ const result = await generativeModel.generateContent(prompt);
7778
+ const response = result.response;
7779
+ const unified = wrapVertexResponse(response);
7780
+ const groundingMetadata = extractGroundingMetadataFromUnified(unified);
7781
+ const searchQueries = extractSearchQueriesFromUnified(unified);
7782
+ return {
7783
+ provider: "gemini",
7784
+ rawResponse: unifiedToRecord(unified),
7785
+ model,
7786
+ groundingSources: groundingMetadata,
7787
+ searchQueries
7788
+ };
7789
+ } else {
7790
+ const searchTool = { googleSearch: {} };
7791
+ const genAI = new GoogleGenerativeAI(input.config.apiKey);
7792
+ const generativeModel = genAI.getGenerativeModel({
7793
+ model,
7794
+ tools: [searchTool]
7795
+ });
7796
+ const result = await generativeModel.generateContent(prompt);
7797
+ const response = result.response;
7798
+ const groundingMetadata = extractGroundingMetadata(response);
7799
+ const searchQueries = extractSearchQueries(response);
7800
+ return {
7801
+ provider: "gemini",
7802
+ rawResponse: responseToRecord(response),
7803
+ model,
7804
+ groundingSources: groundingMetadata,
7805
+ searchQueries
7806
+ };
7807
+ }
7700
7808
  }
7701
7809
  function normalizeResult(raw) {
7702
7810
  const answerText = extractAnswerText(raw.rawResponse);
@@ -7742,6 +7850,22 @@ function extractGroundingMetadata(response) {
7742
7850
  return [];
7743
7851
  }
7744
7852
  }
7853
+ function extractGroundingMetadataFromUnified(response) {
7854
+ try {
7855
+ const candidate = response.candidates?.[0];
7856
+ if (!candidate) return [];
7857
+ const metadata = candidate.groundingMetadata;
7858
+ if (!metadata) return [];
7859
+ const chunks = metadata.groundingChunks;
7860
+ if (!chunks) return [];
7861
+ return chunks.filter((chunk) => chunk.web?.uri).map((chunk) => ({
7862
+ uri: chunk.web.uri,
7863
+ title: chunk.web?.title ?? ""
7864
+ }));
7865
+ } catch {
7866
+ return [];
7867
+ }
7868
+ }
7745
7869
  function extractSearchQueries(response) {
7746
7870
  try {
7747
7871
  const candidate = response.candidates?.[0];
@@ -7750,6 +7874,14 @@ function extractSearchQueries(response) {
7750
7874
  return [];
7751
7875
  }
7752
7876
  }
7877
+ function extractSearchQueriesFromUnified(response) {
7878
+ try {
7879
+ const candidate = response.candidates?.[0];
7880
+ return candidate?.groundingMetadata?.webSearchQueries ?? [];
7881
+ } catch {
7882
+ return [];
7883
+ }
7884
+ }
7753
7885
  function extractCitedDomains(raw) {
7754
7886
  const domains = /* @__PURE__ */ new Set();
7755
7887
  for (const source of raw.groundingSources) {
@@ -7797,10 +7929,16 @@ function extractDomainFromUri(uri) {
7797
7929
  }
7798
7930
  async function generateText(prompt, config) {
7799
7931
  const model = resolveModel(config);
7800
- const genAI = new GoogleGenerativeAI(config.apiKey);
7801
- const generativeModel = genAI.getGenerativeModel({ model });
7802
- const result = await generativeModel.generateContent(prompt);
7803
- return result.response.text();
7932
+ if (isVertexConfig(config)) {
7933
+ const generativeModel = createVertexModel(config, model);
7934
+ const result = await generativeModel.generateContent(prompt);
7935
+ return result.response.candidates?.[0]?.content?.parts?.map((p) => p.text ?? "").join("") ?? "";
7936
+ } else {
7937
+ const genAI = new GoogleGenerativeAI(config.apiKey);
7938
+ const generativeModel = genAI.getGenerativeModel({ model });
7939
+ const result = await generativeModel.generateContent(prompt);
7940
+ return result.response.text();
7941
+ }
7804
7942
  }
7805
7943
  function responseToRecord(response) {
7806
7944
  try {
@@ -7820,13 +7958,34 @@ function responseToRecord(response) {
7820
7958
  return { error: "failed to serialize response" };
7821
7959
  }
7822
7960
  }
7961
+ function unifiedToRecord(response) {
7962
+ try {
7963
+ const candidates = response.candidates?.map((c) => ({
7964
+ content: c.content,
7965
+ finishReason: c.finishReason,
7966
+ groundingMetadata: c.groundingMetadata ? {
7967
+ webSearchQueries: c.groundingMetadata.webSearchQueries,
7968
+ groundingChunks: c.groundingMetadata.groundingChunks
7969
+ } : void 0
7970
+ }));
7971
+ return {
7972
+ candidates: candidates ?? [],
7973
+ usageMetadata: response.usageMetadata ?? null
7974
+ };
7975
+ } catch {
7976
+ return { error: "failed to serialize response" };
7977
+ }
7978
+ }
7823
7979
 
7824
7980
  // ../provider-gemini/src/adapter.ts
7825
7981
  function toGeminiConfig(config) {
7826
7982
  return {
7827
7983
  apiKey: config.apiKey ?? "",
7828
7984
  model: config.model,
7829
- quotaPolicy: config.quotaPolicy
7985
+ quotaPolicy: config.quotaPolicy,
7986
+ vertexProject: config.vertexProject,
7987
+ vertexRegion: config.vertexRegion,
7988
+ vertexCredentials: config.vertexCredentials
7830
7989
  };
7831
7990
  }
7832
7991
  var geminiAdapter = {
@@ -10876,19 +11035,22 @@ async function createServer(opts) {
10876
11035
  }
10877
11036
  log6.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
10878
11037
  const p = providers[k];
10879
- return p?.apiKey || p?.baseUrl;
11038
+ return p?.apiKey || p?.baseUrl || p?.vertexProject;
10880
11039
  }) });
10881
11040
  for (const adapter of API_ADAPTERS) {
10882
11041
  const entry = providers[adapter.name];
10883
11042
  if (!entry) continue;
10884
- const isConfigured = adapter.name === "local" ? !!entry.baseUrl : !!entry.apiKey;
11043
+ const isConfigured = adapter.name === "local" ? !!entry.baseUrl : adapter.name === "gemini" ? !!(entry.apiKey || entry.vertexProject) : !!entry.apiKey;
10885
11044
  if (isConfigured) {
10886
11045
  registry.register(adapter, {
10887
11046
  provider: adapter.name,
10888
11047
  apiKey: entry.apiKey,
10889
11048
  baseUrl: entry.baseUrl,
10890
11049
  model: entry.model,
10891
- quotaPolicy: entry.quota ?? DEFAULT_QUOTA
11050
+ quotaPolicy: entry.quota ?? DEFAULT_QUOTA,
11051
+ vertexProject: entry.vertexProject,
11052
+ vertexRegion: entry.vertexRegion,
11053
+ vertexCredentials: entry.vertexCredentials
10892
11054
  });
10893
11055
  }
10894
11056
  }
@@ -10922,7 +11084,8 @@ async function createServer(opts) {
10922
11084
  modelHint: `e.g. ${adapter.modelRegistry.defaultModel}`,
10923
11085
  model: registry.get(adapter.name)?.config.model,
10924
11086
  configured: !!registry.get(adapter.name),
10925
- quota: registry.get(adapter.name)?.config.quotaPolicy
11087
+ quota: registry.get(adapter.name)?.config.quotaPolicy,
11088
+ vertexConfigured: adapter.name === "gemini" ? !!opts.config.providers?.gemini?.vertexProject : void 0
10926
11089
  }));
10927
11090
  const googleSettingsSummary = {
10928
11091
  configured: Boolean(opts.config.google?.clientId && opts.config.google?.clientSecret)
@@ -11216,7 +11379,12 @@ async function createServer(opts) {
11216
11379
  apiKey: apiKey || existing?.apiKey,
11217
11380
  baseUrl: baseUrl || existing?.baseUrl,
11218
11381
  model: model || existing?.model,
11219
- quota: mergedQuota
11382
+ quota: mergedQuota,
11383
+ // Preserve Vertex AI config (Gemini provider) — these are set via
11384
+ // config file or env vars, not through the dashboard update payload
11385
+ vertexProject: existing?.vertexProject,
11386
+ vertexRegion: existing?.vertexRegion,
11387
+ vertexCredentials: existing?.vertexCredentials
11220
11388
  };
11221
11389
  try {
11222
11390
  saveConfig(opts.config);
@@ -11230,13 +11398,19 @@ async function createServer(opts) {
11230
11398
  apiKey: apiKey || existing?.apiKey,
11231
11399
  baseUrl: baseUrl || existing?.baseUrl,
11232
11400
  model: model || existing?.model,
11233
- quotaPolicy: quota
11401
+ quotaPolicy: quota,
11402
+ vertexProject: existing?.vertexProject,
11403
+ vertexRegion: existing?.vertexRegion,
11404
+ vertexCredentials: existing?.vertexCredentials
11234
11405
  });
11235
11406
  const entry = providerSummary.find((p) => p.name === name);
11236
11407
  if (entry) {
11237
11408
  entry.configured = true;
11238
11409
  entry.model = model || registry.get(name)?.config.model;
11239
11410
  entry.quota = quota;
11411
+ if (name === "gemini") {
11412
+ entry.vertexConfigured = !!opts.config.providers?.[name]?.vertexProject;
11413
+ }
11240
11414
  }
11241
11415
  const afterConfig = summarizeProviderConfig(name, opts.config.providers[name]);
11242
11416
  if (JSON.stringify(beforeConfig) !== JSON.stringify(afterConfig)) {
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  setGoogleAuthConfig,
20
20
  showFirstRunNotice,
21
21
  trackEvent
22
- } from "./chunk-NVCAUQ33.js";
22
+ } from "./chunk-727N35MW.js";
23
23
 
24
24
  // src/cli.ts
25
25
  import { pathToFileURL } from "url";
@@ -4174,6 +4174,10 @@ var envSchema = z.object({
4174
4174
  GEMINI_MAX_CONCURRENCY: z.coerce.number().int().positive().default(2),
4175
4175
  GEMINI_MAX_REQUESTS_PER_MINUTE: z.coerce.number().int().positive().default(10),
4176
4176
  GEMINI_MAX_REQUESTS_PER_DAY: z.coerce.number().int().positive().default(1e3),
4177
+ // Gemini Vertex AI (alternative to API key auth)
4178
+ GEMINI_VERTEX_PROJECT: z.string().optional(),
4179
+ GEMINI_VERTEX_REGION: z.string().optional(),
4180
+ GEMINI_VERTEX_CREDENTIALS: z.string().optional(),
4177
4181
  // OpenAI
4178
4182
  OPENAI_API_KEY: z.string().optional(),
4179
4183
  OPENAI_MODEL: z.string().optional(),
@@ -4199,6 +4203,9 @@ var bootstrapEnvSchema = z.object({
4199
4203
  CANONRY_DATABASE_PATH: z.string().optional(),
4200
4204
  GEMINI_API_KEY: z.string().optional(),
4201
4205
  GEMINI_MODEL: z.string().optional(),
4206
+ GEMINI_VERTEX_PROJECT: z.string().optional(),
4207
+ GEMINI_VERTEX_REGION: z.string().optional(),
4208
+ GEMINI_VERTEX_CREDENTIALS: z.string().optional(),
4202
4209
  OPENAI_API_KEY: z.string().optional(),
4203
4210
  OPENAI_MODEL: z.string().optional(),
4204
4211
  ANTHROPIC_API_KEY: z.string().optional(),
@@ -4215,15 +4222,18 @@ function getBootstrapEnv(source, overrides) {
4215
4222
  const filtered = overrides ? Object.fromEntries(Object.entries(overrides).filter(([, v]) => v != null)) : {};
4216
4223
  const parsed = bootstrapEnvSchema.parse({ ...source, ...filtered });
4217
4224
  const providers = {};
4218
- if (parsed.GEMINI_API_KEY) {
4225
+ if (parsed.GEMINI_API_KEY || parsed.GEMINI_VERTEX_PROJECT) {
4219
4226
  providers.gemini = {
4220
- apiKey: parsed.GEMINI_API_KEY,
4227
+ apiKey: parsed.GEMINI_API_KEY ?? "",
4221
4228
  model: parsed.GEMINI_MODEL || "gemini-3-flash",
4222
4229
  quota: providerQuotaPolicySchema.parse({
4223
4230
  maxConcurrency: 2,
4224
4231
  maxRequestsPerMinute: 10,
4225
4232
  maxRequestsPerDay: 500
4226
- })
4233
+ }),
4234
+ vertexProject: parsed.GEMINI_VERTEX_PROJECT,
4235
+ vertexRegion: parsed.GEMINI_VERTEX_REGION,
4236
+ vertexCredentials: parsed.GEMINI_VERTEX_CREDENTIALS
4227
4237
  };
4228
4238
  }
4229
4239
  if (parsed.OPENAI_API_KEY) {
package/dist/index.d.ts CHANGED
@@ -8,6 +8,12 @@ interface ProviderConfigEntry {
8
8
  baseUrl?: string;
9
9
  model?: string;
10
10
  quota?: ProviderQuotaPolicy;
11
+ /** Vertex AI GCP project ID (Gemini provider only) */
12
+ vertexProject?: string;
13
+ /** Vertex AI region, e.g. "us-central1" (Gemini provider only) */
14
+ vertexRegion?: string;
15
+ /** Path to service account JSON for Vertex AI auth (falls back to ADC) */
16
+ vertexCredentials?: string;
11
17
  }
12
18
  interface CdpConfigEntry {
13
19
  host?: string;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-NVCAUQ33.js";
4
+ } from "./chunk-727N35MW.js";
5
5
  export {
6
6
  createServer,
7
7
  loadConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "1.26.2",
3
+ "version": "1.27.2",
4
4
  "type": "module",
5
5
  "description": "The ultimate open-source AEO monitoring tool - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -36,6 +36,7 @@
36
36
  "dependencies": {
37
37
  "@anthropic-ai/sdk": "^0.78.0",
38
38
  "@fastify/static": "^8.1.0",
39
+ "@google-cloud/vertexai": "^1.10.3",
39
40
  "@google/generative-ai": "^0.24.1",
40
41
  "better-sqlite3": "^12.6.2",
41
42
  "drizzle-orm": "^0.45.1",
@@ -52,18 +53,18 @@
52
53
  "@types/node-cron": "^3.0.11",
53
54
  "tsup": "^8.5.1",
54
55
  "tsx": "^4.19.0",
55
- "@ainyc/canonry-contracts": "0.0.0",
56
56
  "@ainyc/canonry-api-routes": "0.0.0",
57
- "@ainyc/canonry-db": "0.0.0",
58
- "@ainyc/canonry-config": "0.0.0",
59
57
  "@ainyc/canonry-provider-claude": "0.0.0",
58
+ "@ainyc/canonry-contracts": "0.0.0",
60
59
  "@ainyc/canonry-provider-gemini": "0.0.0",
61
- "@ainyc/canonry-provider-cdp": "0.0.0",
60
+ "@ainyc/canonry-config": "0.0.0",
62
61
  "@ainyc/canonry-provider-local": "0.0.0",
63
- "@ainyc/canonry-integration-bing": "0.0.0",
62
+ "@ainyc/canonry-db": "0.0.0",
63
+ "@ainyc/canonry-provider-cdp": "0.0.0",
64
64
  "@ainyc/canonry-integration-google": "0.0.0",
65
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
65
66
  "@ainyc/canonry-provider-openai": "0.0.0",
66
- "@ainyc/canonry-provider-perplexity": "0.0.0"
67
+ "@ainyc/canonry-integration-bing": "0.0.0"
67
68
  },
68
69
  "scripts": {
69
70
  "build": "tsup && tsx build-web.ts",