@foldrun/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -10
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/src/account.d.ts +45 -0
- package/dist/src/account.js +130 -0
- package/dist/src/api-tools.d.ts +8 -0
- package/dist/src/api-tools.js +22 -4
- package/dist/src/approvals.d.ts +104 -1
- package/dist/src/approvals.js +192 -2
- package/dist/src/browser-pod.d.ts +129 -0
- package/dist/src/browser-pod.js +284 -0
- package/dist/src/budget.d.ts +32 -0
- package/dist/src/budget.js +177 -0
- package/dist/src/catalog.js +3 -1
- package/dist/src/clock.d.ts +49 -0
- package/dist/src/clock.js +121 -0
- package/dist/src/completions.js +17 -8
- package/dist/src/confine.d.ts +22 -0
- package/dist/src/confine.js +224 -4
- package/dist/src/deploy.d.ts +33 -0
- package/dist/src/deploy.js +145 -6
- package/dist/src/egress.d.ts +26 -2
- package/dist/src/egress.js +8 -1
- package/dist/src/eol.d.ts +2 -0
- package/dist/src/eol.js +7 -0
- package/dist/src/evals.d.ts +9 -0
- package/dist/src/evals.js +26 -13
- package/dist/src/flow-lint.d.ts +12 -0
- package/dist/src/flow-lint.js +331 -1
- package/dist/src/flow-patterns.d.ts +141 -0
- package/dist/src/flow-patterns.js +568 -0
- package/dist/src/gitrepo.d.ts +18 -0
- package/dist/src/gitrepo.js +49 -0
- package/dist/src/host-env.d.ts +8 -0
- package/dist/src/host-env.js +48 -0
- package/dist/src/json-schema.d.ts +12 -0
- package/dist/src/json-schema.js +202 -0
- package/dist/src/kinds.js +1 -1
- package/dist/src/language.d.ts +17 -0
- package/dist/src/language.js +55 -0
- package/dist/src/layout.d.ts +69 -0
- package/dist/src/layout.js +200 -0
- package/dist/src/library.d.ts +9 -0
- package/dist/src/library.js +43 -4
- package/dist/src/limits.d.ts +84 -0
- package/dist/src/limits.js +287 -0
- package/dist/src/locale.d.ts +47 -0
- package/dist/src/locale.js +177 -0
- package/dist/src/mail.d.ts +88 -0
- package/dist/src/mail.js +245 -0
- package/dist/src/model-probe.d.ts +3 -0
- package/dist/src/model-probe.js +1 -0
- package/dist/src/notify.d.ts +132 -8
- package/dist/src/notify.js +360 -58
- package/dist/src/openapi.js +5 -3
- package/dist/src/operator.d.ts +201 -0
- package/dist/src/operator.js +251 -0
- package/dist/src/paths.d.ts +33 -0
- package/dist/src/paths.js +87 -0
- package/dist/src/platform.d.ts +144 -4
- package/dist/src/platform.js +27 -3
- package/dist/src/provider-health.d.ts +55 -0
- package/dist/src/provider-health.js +233 -0
- package/dist/src/providers.d.ts +481 -0
- package/dist/src/providers.js +1105 -6
- package/dist/src/refusal.d.ts +83 -0
- package/dist/src/refusal.js +281 -0
- package/dist/src/run-container.d.ts +206 -17
- package/dist/src/run-container.js +547 -80
- package/dist/src/runner.d.ts +179 -10
- package/dist/src/runner.js +1423 -169
- package/dist/src/runtime.d.ts +52 -0
- package/dist/src/runtime.js +493 -58
- package/dist/src/script-tools.d.ts +15 -2
- package/dist/src/script-tools.js +44 -4
- package/dist/src/secret-files.d.ts +6 -5
- package/dist/src/secret-files.js +24 -11
- package/dist/src/secret-health.d.ts +83 -0
- package/dist/src/secret-health.js +177 -0
- package/dist/src/secrets.d.ts +38 -1
- package/dist/src/secrets.js +197 -96
- package/dist/src/starter.d.ts +9 -0
- package/dist/src/starter.js +95 -11
- package/dist/src/step-exec.d.ts +106 -1
- package/dist/src/step-exec.js +558 -85
- package/dist/src/step-removal.d.ts +15 -0
- package/dist/src/step-removal.js +49 -0
- package/dist/src/storage.d.ts +23 -4
- package/dist/src/storage.js +125 -23
- package/dist/src/store.d.ts +492 -38
- package/dist/src/store.js +882 -69
- package/dist/src/subagents.d.ts +75 -0
- package/dist/src/subagents.js +149 -0
- package/dist/src/tar.js +2 -1
- package/dist/src/test-mode.d.ts +111 -0
- package/dist/src/test-mode.js +285 -0
- package/dist/src/tool-names.d.ts +13 -1
- package/dist/src/tool-names.js +38 -11
- package/dist/src/tool-programs.d.ts +26 -0
- package/dist/src/tool-programs.js +172 -0
- package/dist/src/tool-test.js +152 -33
- package/dist/src/translator.d.ts +8 -2
- package/dist/src/translator.js +71 -24
- package/dist/src/trigger-log.d.ts +43 -0
- package/dist/src/trigger-log.js +122 -0
- package/dist/src/triggers.d.ts +23 -0
- package/dist/src/triggers.js +101 -12
- package/dist/src/untrusted-fetch.d.ts +11 -0
- package/dist/src/untrusted-fetch.js +77 -0
- package/dist/src/web-actions.d.ts +91 -0
- package/dist/src/web-actions.js +199 -0
- package/dist/src/webhook.d.ts +19 -3
- package/dist/src/webhook.js +50 -11
- package/index.ts +3 -0
- package/package.json +5 -5
- package/src/account.ts +149 -0
- package/src/api-tools.ts +32 -4
- package/src/approvals.ts +253 -2
- package/src/browser-pod.ts +313 -0
- package/src/budget.ts +178 -0
- package/src/catalog.ts +3 -1
- package/src/clock.ts +149 -0
- package/src/completions.ts +17 -8
- package/src/confine.ts +224 -4
- package/src/deploy.ts +137 -6
- package/src/egress.ts +32 -4
- package/src/eol.ts +7 -0
- package/src/evals.ts +36 -11
- package/src/flow-lint.ts +371 -1
- package/src/flow-patterns.ts +597 -0
- package/src/gitrepo.ts +48 -0
- package/src/host-env.ts +51 -0
- package/src/json-schema.ts +184 -0
- package/src/kinds.ts +1 -1
- package/src/language.ts +62 -0
- package/src/layout.ts +228 -0
- package/src/library.ts +50 -4
- package/src/limits.ts +290 -0
- package/src/locale.ts +217 -0
- package/src/mail.ts +316 -0
- package/src/model-probe.ts +4 -0
- package/src/notify.ts +391 -44
- package/src/openapi.ts +5 -3
- package/src/operator.ts +300 -0
- package/src/paths.ts +83 -0
- package/src/platform.ts +158 -5
- package/src/provider-health.ts +275 -0
- package/src/providers.ts +1368 -6
- package/src/refusal.ts +320 -0
- package/src/run-container.ts +611 -76
- package/src/runner.ts +1463 -165
- package/src/runtime.ts +487 -55
- package/src/script-tools.ts +55 -6
- package/src/secret-files.ts +22 -10
- package/src/secret-health.ts +230 -0
- package/src/secrets.ts +191 -95
- package/src/starter.ts +96 -11
- package/src/step-exec.ts +548 -41
- package/src/step-removal.ts +59 -0
- package/src/storage.ts +149 -24
- package/src/store.ts +1227 -74
- package/src/subagents.ts +189 -0
- package/src/tar.ts +2 -1
- package/src/test-mode.ts +311 -0
- package/src/tool-names.ts +40 -11
- package/src/tool-programs.ts +179 -0
- package/src/tool-test.ts +159 -32
- package/src/translator.ts +74 -23
- package/src/trigger-log.ts +160 -0
- package/src/triggers.ts +102 -12
- package/src/untrusted-fetch.ts +88 -0
- package/src/web-actions.ts +237 -0
- package/src/webhook.ts +58 -11
- package/templates/hello/agents/curator/agent.md +1 -1
- package/templates/hello/agents/notetaker/agent.md +1 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Is the key still good?
|
|
2
|
+
//
|
|
3
|
+
// A customer's own model key can be revoked, expire, run out of credit or
|
|
4
|
+
// have its base URL moved, and today the platform finds out the way the
|
|
5
|
+
// customer does: a desk fails at five in the morning. Twenty-seven of the
|
|
6
|
+
// thirty provider presets have never had an agent run through them from
|
|
7
|
+
// this runtime at all, so "it worked when we set it up" is doing a lot of
|
|
8
|
+
// work in that sentence.
|
|
9
|
+
//
|
|
10
|
+
// This is deliberately NOT the tool-loop probe next door. `foldrun probe`
|
|
11
|
+
// asks "can this model drive an agent", which is a question about a model
|
|
12
|
+
// and is asked once. This asks "does this endpoint still take our key",
|
|
13
|
+
// which is a question about a credential and has to be asked repeatedly —
|
|
14
|
+
// so it has to be cheap. One request, one token of output, no SDK, no tool
|
|
15
|
+
// loop, no agent. A fraction of a cent, and it answers the thing that
|
|
16
|
+
// actually breaks.
|
|
17
|
+
//
|
|
18
|
+
// It reads the same provider block a run reads, resolved the same way, so a
|
|
19
|
+
// pass here means a run would reach the same place with the same key.
|
|
20
|
+
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import matter from "gray-matter";
|
|
24
|
+
import { getSecret } from "./secrets.ts";
|
|
25
|
+
import { noteSecretUse, healthKey } from "./secret-health.ts";
|
|
26
|
+
import { accountDir, workspaceDir, listWorkspaces, listAgents, parseProvider, type ProviderSpec } from "./store.ts";
|
|
27
|
+
import { fetchUntrusted } from "./untrusted-fetch.ts";
|
|
28
|
+
|
|
29
|
+
export type HealthVerdict = "ok" | "credential" | "not-found" | "busy" | "unreachable" | "provider-error";
|
|
30
|
+
|
|
31
|
+
export interface ProviderCheck {
|
|
32
|
+
/** How this provider is written in the file: its preset name, or its host. */
|
|
33
|
+
provider: string;
|
|
34
|
+
/** The model id actually asked for — what a run would send. */
|
|
35
|
+
model: string;
|
|
36
|
+
verdict: HealthVerdict;
|
|
37
|
+
ok: boolean;
|
|
38
|
+
status: number | null;
|
|
39
|
+
ms: number;
|
|
40
|
+
/** One sentence a person can act on, in the provider's own words where
|
|
41
|
+
* they said anything useful. */
|
|
42
|
+
detail: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** How long to wait. A provider slower than this is not one a step should
|
|
46
|
+
* be waiting on either, and the check must never hold a tick. */
|
|
47
|
+
const TIMEOUT_MS = 15_000;
|
|
48
|
+
|
|
49
|
+
/** What each status means for a credential, and what a person does about
|
|
50
|
+
* it. Kept as a table because the mapping IS the feature: "401" tells you
|
|
51
|
+
* nothing you can act on, "the key is no longer accepted" tells you
|
|
52
|
+
* everything. */
|
|
53
|
+
function verdictFor(status: number): { verdict: HealthVerdict; detail: string } {
|
|
54
|
+
if (status >= 200 && status < 300) return { verdict: "ok", detail: "the key was accepted and the model answered" };
|
|
55
|
+
if (status === 401 || status === 403) {
|
|
56
|
+
return { verdict: "credential", detail: "the key is no longer accepted — revoked, rotated, or wrong for this endpoint" };
|
|
57
|
+
}
|
|
58
|
+
if (status === 402) return { verdict: "credential", detail: "the account behind this key is out of credit" };
|
|
59
|
+
if (status === 404) {
|
|
60
|
+
return { verdict: "not-found", detail: "the endpoint or the model id does not exist there — check base_url and models:" };
|
|
61
|
+
}
|
|
62
|
+
if (status === 429) {
|
|
63
|
+
// Not a health problem. A busy provider is a provider that is working,
|
|
64
|
+
// and reporting it as broken would train people to ignore the alert.
|
|
65
|
+
return { verdict: "busy", detail: "rate limited right now — the key is fine" };
|
|
66
|
+
}
|
|
67
|
+
if (status >= 500) return { verdict: "provider-error", detail: "the provider is failing at its end" };
|
|
68
|
+
return { verdict: "provider-error", detail: `unexpected HTTP ${status}` };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** `${SECRET}` in a token or header, filled from this account's vault. */
|
|
72
|
+
function resolve(tenant: string, workspace: string | null, raw: string): string {
|
|
73
|
+
return raw.replace(/\$\{([A-Z][A-Z0-9_]*)\}/g, (whole, name) => {
|
|
74
|
+
const hit = getSecret(tenant, name, workspace ?? undefined);
|
|
75
|
+
return hit ? hit.value : whole;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* One request at the provider, in the wire format it speaks, asking for a
|
|
81
|
+
* single token. Never throws: an unreachable host is a verdict, not an
|
|
82
|
+
* exception, because this runs on a tick.
|
|
83
|
+
*/
|
|
84
|
+
export async function checkProvider(
|
|
85
|
+
tenant: string,
|
|
86
|
+
workspace: string | null,
|
|
87
|
+
spec: ProviderSpec,
|
|
88
|
+
model: string,
|
|
89
|
+
): Promise<ProviderCheck> {
|
|
90
|
+
const label = spec.name || (() => {
|
|
91
|
+
try {
|
|
92
|
+
return new URL(spec.baseUrl).host;
|
|
93
|
+
} catch {
|
|
94
|
+
return spec.baseUrl || "provider";
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
const began = Date.now();
|
|
98
|
+
const fail = (verdict: HealthVerdict, detail: string, status: number | null = null): ProviderCheck => ({
|
|
99
|
+
provider: label,
|
|
100
|
+
model,
|
|
101
|
+
verdict,
|
|
102
|
+
ok: false,
|
|
103
|
+
status,
|
|
104
|
+
ms: Date.now() - began,
|
|
105
|
+
detail,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const token = resolve(tenant, workspace, spec.token);
|
|
109
|
+
if (!token || token.includes("${")) {
|
|
110
|
+
return fail("credential", `the secret named in provider.token is not in this account's vault (${spec.token})`);
|
|
111
|
+
}
|
|
112
|
+
if (!spec.baseUrl) return fail("unreachable", "this provider block has no base_url");
|
|
113
|
+
|
|
114
|
+
// The three wire formats, each asking for the smallest possible answer.
|
|
115
|
+
const base = spec.baseUrl.replace(/\/+$/, "");
|
|
116
|
+
const anthropic = spec.format === "anthropic";
|
|
117
|
+
const url = anthropic
|
|
118
|
+
? `${base}/v1/messages`
|
|
119
|
+
: spec.format === "responses"
|
|
120
|
+
? `${base}/responses`
|
|
121
|
+
: `${base}/chat/completions`;
|
|
122
|
+
const body = anthropic
|
|
123
|
+
? { model, max_tokens: 1, messages: [{ role: "user", content: "hi" }] }
|
|
124
|
+
: spec.format === "responses"
|
|
125
|
+
? { model, max_output_tokens: 16, input: "hi" }
|
|
126
|
+
: { model, max_tokens: 1, messages: [{ role: "user", content: "hi" }] };
|
|
127
|
+
|
|
128
|
+
const headers: Record<string, string> = { "content-type": "application/json" };
|
|
129
|
+
// An Anthropic-format endpoint takes the key in whichever header its
|
|
130
|
+
// `auth:` says; everything else is a bearer token.
|
|
131
|
+
if (anthropic && spec.auth === "x-api-key") {
|
|
132
|
+
headers["x-api-key"] = token;
|
|
133
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
134
|
+
} else {
|
|
135
|
+
headers.authorization = `Bearer ${token}`;
|
|
136
|
+
if (anthropic) headers["anthropic-version"] = "2023-06-01";
|
|
137
|
+
}
|
|
138
|
+
for (const [k, v] of Object.entries(spec.headers ?? {})) headers[k.toLowerCase()] = resolve(tenant, workspace, v);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetchUntrusted(url, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers,
|
|
144
|
+
body: JSON.stringify(body),
|
|
145
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
146
|
+
}, { sameOrigin: true });
|
|
147
|
+
const { verdict, detail } = verdictFor(res.status);
|
|
148
|
+
// The check is itself a use of the credential, and the most informative
|
|
149
|
+
// one there is: it was made for exactly this purpose.
|
|
150
|
+
const named = spec.token.match(/^\$\{([A-Z][A-Z0-9_]*)\}$/)?.[1];
|
|
151
|
+
if (named) {
|
|
152
|
+
let host = spec.baseUrl;
|
|
153
|
+
try {
|
|
154
|
+
host = new URL(spec.baseUrl).host;
|
|
155
|
+
} catch {
|
|
156
|
+
// an unparseable base_url is its own problem, reported above
|
|
157
|
+
}
|
|
158
|
+
const hit = getSecret(tenant, named, workspace ?? undefined);
|
|
159
|
+
noteSecretUse(tenant, healthKey(named, hit?.scope === "workspace" ? "workspace" : "account", workspace), { host, status: res.status });
|
|
160
|
+
}
|
|
161
|
+
// The provider's own words when it refused. They are the difference
|
|
162
|
+
// between "fix your key" and "fix your model id", and they are only
|
|
163
|
+
// ever in the body.
|
|
164
|
+
let said = "";
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
said = (await res.text().catch(() => "")).replace(/\s+/g, " ").slice(0, 300);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
provider: label,
|
|
170
|
+
model,
|
|
171
|
+
verdict,
|
|
172
|
+
// Busy counts as healthy: the key worked, the provider is simply full.
|
|
173
|
+
ok: verdict === "ok" || verdict === "busy",
|
|
174
|
+
status: res.status,
|
|
175
|
+
ms: Date.now() - began,
|
|
176
|
+
detail: said ? `${detail} — ${said}` : detail,
|
|
177
|
+
};
|
|
178
|
+
} catch (err) {
|
|
179
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
180
|
+
return fail("unreachable", /timeout|aborted/i.test(message) ? `no answer within ${TIMEOUT_MS / 1000}s` : message);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
/** A provider block in use, and where it is declared — because "which file
|
|
186
|
+
* do I edit" is the next question after "this key is dead". */
|
|
187
|
+
export interface ProviderInUse {
|
|
188
|
+
spec: ProviderSpec;
|
|
189
|
+
/** The workspace whose vault resolves this block's secrets. Null for the
|
|
190
|
+
* account's own block, which resolves against the account vault. */
|
|
191
|
+
workspace: string | null;
|
|
192
|
+
/** Human-readable origin: "account", "rank-desk", "rank-desk/writer". */
|
|
193
|
+
declaredIn: string;
|
|
194
|
+
/** The model id a run would send for the cheapest tier, which is what a
|
|
195
|
+
* health check should spend. */
|
|
196
|
+
model: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** The cheapest tier's id for a provider block: what it calls `fast`, or
|
|
200
|
+
* our own fast model when it remaps nothing. */
|
|
201
|
+
function cheapestModel(spec: ProviderSpec): string {
|
|
202
|
+
return spec.models.fast || spec.models.default || spec.models.max || "claude-haiku-4-5-20251001";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function frontmatterOf(file: string): Record<string, unknown> {
|
|
206
|
+
try {
|
|
207
|
+
return (matter(fs.readFileSync(file, "utf8")).data ?? {}) as Record<string, unknown>;
|
|
208
|
+
} catch {
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Every distinct provider block this account would actually use, read from
|
|
215
|
+
* the same three places a run reads them: the account, each workspace, each
|
|
216
|
+
* agent.
|
|
217
|
+
*
|
|
218
|
+
* Deduplicated by endpoint and token, because ten agents sharing one gateway
|
|
219
|
+
* are one credential and checking it ten times spends ten times as much to
|
|
220
|
+
* learn the same fact. The origin recorded is the first one seen, which is
|
|
221
|
+
* the broadest — an account block covers everything under it.
|
|
222
|
+
*/
|
|
223
|
+
export function providersInUse(tenant: string): ProviderInUse[] {
|
|
224
|
+
const out: ProviderInUse[] = [];
|
|
225
|
+
const seen = new Set<string>();
|
|
226
|
+
|
|
227
|
+
const add = (raw: unknown, workspace: string | null, declaredIn: string) => {
|
|
228
|
+
const spec = parseProvider(raw);
|
|
229
|
+
if (!spec || !spec.baseUrl) return;
|
|
230
|
+
// The token is part of the identity: the same gateway with two different
|
|
231
|
+
// keys is two credentials, and either can die on its own.
|
|
232
|
+
const key = `${workspace ?? ""}|${spec.baseUrl}|${spec.token}|${cheapestModel(spec)}`;
|
|
233
|
+
if (seen.has(key)) return;
|
|
234
|
+
seen.add(key);
|
|
235
|
+
out.push({ spec, workspace, declaredIn, model: cheapestModel(spec) });
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
add(frontmatterOf(path.join(accountDir(tenant), "AGENTS.md")).provider, null, "account");
|
|
239
|
+
for (const ws of safeList(() => listWorkspaces(tenant))) {
|
|
240
|
+
const dir = workspaceDir(tenant, ws.name);
|
|
241
|
+
add(frontmatterOf(path.join(dir, "AGENTS.md")).provider, ws.name, ws.name);
|
|
242
|
+
for (const agent of safeList(() => listAgents(tenant, ws.name))) {
|
|
243
|
+
add(
|
|
244
|
+
frontmatterOf(path.join(dir, "agents", agent.name, "agent.md")).provider,
|
|
245
|
+
ws.name,
|
|
246
|
+
`${ws.name}/${agent.name}`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function safeList<T>(fn: () => T[]): T[] {
|
|
254
|
+
try {
|
|
255
|
+
return fn();
|
|
256
|
+
} catch {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Check every provider this account uses, one at a time.
|
|
263
|
+
*
|
|
264
|
+
* Serial on purpose: this is housekeeping on a tick, the whole point is that
|
|
265
|
+
* it is cheap, and firing twelve model requests at once to save eight
|
|
266
|
+
* seconds nobody is waiting for would be a strange trade.
|
|
267
|
+
*/
|
|
268
|
+
export async function checkAccountProviders(tenant: string): Promise<(ProviderCheck & { declaredIn: string })[]> {
|
|
269
|
+
const results: (ProviderCheck & { declaredIn: string })[] = [];
|
|
270
|
+
for (const use of providersInUse(tenant)) {
|
|
271
|
+
const check = await checkProvider(tenant, use.workspace, use.spec, use.model);
|
|
272
|
+
results.push({ ...check, declaredIn: use.declaredIn });
|
|
273
|
+
}
|
|
274
|
+
return results;
|
|
275
|
+
}
|