e2e 0.16.0 → 0.17.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/dist/.build.json +1 -1
- package/dist/agent/act.js +7 -4
- package/dist/agent/action-dispatcher.js +4 -5
- package/dist/agent/actions.d.ts +32 -23
- package/dist/agent/actions.js +38 -24
- package/dist/agent/model/sdk.js +2 -0
- package/dist/agent/observation-feed.d.ts +5 -6
- package/dist/agent/observation-feed.js +5 -7
- package/dist/agent/observation.d.ts +40 -11
- package/dist/agent/observation.js +109 -59
- package/dist/agent/point-tap.d.ts +8 -8
- package/dist/agent/point-tap.js +1 -1
- package/dist/agent/replay.d.ts +19 -8
- package/dist/agent/replay.js +11 -14
- package/dist/agent/scroll-target.d.ts +2 -3
- package/dist/agent/step-cache.d.ts +41 -14
- package/dist/agent/step-cache.js +147 -62
- package/dist/agent/tool-loop.js +2 -0
- package/dist/cache/anchors.d.ts +72 -23
- package/dist/cache/anchors.js +233 -60
- package/dist/cache/context.d.ts +25 -14
- package/dist/cache/context.js +50 -8
- package/dist/cache/decide.d.ts +2 -2
- package/dist/cache/decide.js +4 -6
- package/dist/cache/identity.d.ts +18 -4
- package/dist/cache/identity.js +9 -4
- package/dist/cache/recorder.d.ts +15 -1
- package/dist/cache/recorder.js +36 -23
- package/dist/cache/rekeyed.d.ts +46 -0
- package/dist/cache/rekeyed.js +89 -0
- package/dist/cache/relocate.d.ts +14 -14
- package/dist/cache/relocate.js +33 -25
- package/dist/cache/route.d.ts +32 -33
- package/dist/cache/route.js +94 -50
- package/dist/cache/store.d.ts +6 -0
- package/dist/cache/store.js +21 -1
- package/dist/cache/trace.d.ts +53 -14
- package/dist/cache/trace.js +70 -28
- package/dist/cli/index.js +8 -8
- package/dist/cli/init/engines.d.ts +1 -3
- package/dist/cli/init/engines.js +2 -2
- package/dist/cli/init/gateways.d.ts +1 -1
- package/dist/cli/init/gateways.js +11 -1
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/cli/init/versions.d.ts +0 -8
- package/dist/cli/init/versions.js +1 -10
- package/dist/config/resolve.d.ts +1 -0
- package/dist/config/resolve.js +33 -1
- package/dist/engine/index.d.ts +2 -2
- package/dist/expect/async.js +2 -1
- package/dist/expect/poll-scope.d.ts +52 -0
- package/dist/expect/poll-scope.js +113 -0
- package/dist/expect/poll.js +31 -4
- package/dist/expect/values.js +11 -4
- package/dist/internal/abandoned.d.ts +11 -0
- package/dist/internal/abandoned.js +23 -0
- package/dist/internal/brands.d.ts +2 -0
- package/dist/internal/brands.js +2 -0
- package/dist/internal/client-identity.d.ts +9 -0
- package/dist/internal/client-identity.js +14 -0
- package/dist/internal/redact.d.ts +7 -4
- package/dist/internal/redact.js +116 -23
- package/dist/internal/time.d.ts +6 -0
- package/dist/internal/time.js +25 -13
- package/dist/internal/urls.d.ts +9 -5
- package/dist/internal/urls.js +19 -10
- package/dist/locator/engine.d.ts +12 -2
- package/dist/locator/engine.js +8 -3
- package/dist/locator/screen.js +28 -4
- package/dist/mcp/catalog.d.ts +3 -2
- package/dist/mcp/catalog.js +6 -6
- package/dist/mcp/session.js +1 -1
- package/dist/oauth/chatgpt.js +3 -23
- package/dist/oauth/cli.js +1 -0
- package/dist/oauth/copilot.d.ts +5 -4
- package/dist/oauth/copilot.js +80 -7
- package/dist/oauth/grok.js +2 -1
- package/dist/oauth/models.js +2 -1
- package/dist/oauth/opencode-console.d.ts +10 -0
- package/dist/oauth/opencode-console.js +133 -0
- package/dist/oauth/providers/github-copilot.d.ts +12 -0
- package/dist/oauth/providers/github-copilot.js +77 -23
- package/dist/oauth/providers/opencode-console.d.ts +31 -0
- package/dist/oauth/providers/opencode-console.js +148 -0
- package/dist/oauth/providers.d.ts +2 -6
- package/dist/oauth/providers.js +3 -7
- package/dist/oauth/responses.d.ts +9 -0
- package/dist/oauth/responses.js +23 -0
- package/dist/report/build.d.ts +26 -0
- package/dist/report/build.js +16 -8
- package/dist/report/failure-text.d.ts +6 -1
- package/dist/report/failure-text.js +14 -4
- package/dist/report/format.d.ts +10 -5
- package/dist/report/format.js +12 -5
- package/dist/report/junit.js +11 -12
- package/dist/report/list.d.ts +1 -0
- package/dist/report/list.js +38 -18
- package/dist/report/markdown.d.ts +1 -1
- package/dist/report/markdown.js +37 -14
- package/dist/report/repeats.d.ts +6 -1
- package/dist/report/repeats.js +12 -3
- package/dist/run/artifacts.d.ts +18 -0
- package/dist/run/artifacts.js +68 -1
- package/dist/run/execute.d.ts +9 -0
- package/dist/run/execute.js +49 -24
- package/dist/run/extended-fixtures.js +6 -2
- package/dist/run/failure-evidence.js +4 -2
- package/dist/run/fixtures.js +6 -8
- package/dist/run/in-process.d.ts +2 -0
- package/dist/run/in-process.js +1 -0
- package/dist/run/last-run.d.ts +42 -8
- package/dist/run/last-run.js +121 -14
- package/dist/run/realm.d.ts +4 -1
- package/dist/run/realm.js +20 -6
- package/dist/run/records.d.ts +16 -0
- package/dist/run/records.js +26 -0
- package/dist/run/retry.d.ts +3 -1
- package/dist/run/retry.js +4 -2
- package/dist/run/runner.d.ts +1 -1
- package/dist/run/runner.js +69 -30
- package/dist/run/serial.d.ts +2 -0
- package/dist/run/serial.js +8 -5
- package/dist/run/steps.d.ts +0 -6
- package/dist/run/steps.js +3 -20
- package/dist/run/worker/entry.js +5 -4
- package/dist/run/worker/protocol.d.ts +2 -0
- package/dist/run/worker/session.d.ts +2 -0
- package/dist/run/worker/session.js +1 -0
- package/dist/telemetry/events.js +1 -0
- package/dist/types.d.ts +9 -3
- package/docs/agents.mdx +6 -3
- package/docs/assertions.mdx +94 -0
- package/docs/authentication.mdx +3 -1
- package/docs/browser.mdx +42 -6
- package/docs/cache.mdx +64 -18
- package/docs/ci.mdx +11 -7
- package/docs/debugging.mdx +6 -3
- package/docs/executors.mdx +4 -2
- package/docs/github.mdx +12 -4
- package/docs/{writing-tests.mdx → goals.mdx} +21 -101
- package/docs/index.mdx +4 -4
- package/docs/integrations/kernel.mdx +1 -1
- package/docs/locators.mdx +64 -0
- package/docs/migrate/cypress.mdx +2 -2
- package/docs/migrate/maestro.mdx +2 -2
- package/docs/migrate/playwright.mdx +19 -16
- package/docs/migrate/selenium.mdx +2 -2
- package/docs/mobile.mdx +21 -4
- package/docs/models.mdx +53 -7
- package/docs/physical-devices.mdx +76 -0
- package/docs/quickstart.mdx +4 -3
- package/docs/reference/app.mdx +1 -1
- package/docs/reference/cli.mdx +30 -13
- package/docs/reference/config.mdx +25 -7
- package/docs/reference/engine.mdx +3 -3
- package/docs/reference/environment.mdx +8 -1
- package/docs/reference/errors.mdx +5 -5
- package/docs/reference/expect.mdx +34 -12
- package/docs/reference/mobile.mdx +23 -13
- package/docs/reference/reporters.mdx +8 -2
- package/docs/reference/screen.mdx +30 -7
- package/docs/reference/test.mdx +11 -4
- package/docs/reference/web.mdx +125 -21
- package/docs/security.mdx +26 -16
- package/docs/subscriptions.mdx +91 -12
- package/docs/telemetry.mdx +1 -1
- package/package.json +16 -3
- package/schema/report-v1.schema.json +14 -1
- package/skills/e2e/SKILL.md +2 -2
- package/skills/e2e/references/agent.md +22 -11
- package/skills/e2e/references/debugging.md +4 -4
- package/skills/e2e/references/running.md +13 -8
- package/skills/e2e/references/setup.md +36 -15
- package/skills/e2e/references/writing-tests.md +34 -17
package/dist/internal/redact.js
CHANGED
|
@@ -63,24 +63,31 @@ function compile(values) {
|
|
|
63
63
|
});
|
|
64
64
|
// `split` on a capturing pattern returns the markers at the odd indexes; those pass through untouched.
|
|
65
65
|
const redact = (text) => text.split(known).map((piece, index) => (index % 2 === 1 ? piece : rewrite(piece))).join('');
|
|
66
|
-
const valueKeys = entries.map(([, value]) =>
|
|
66
|
+
const valueKeys = entries.map(([, value]) => readingKey(value).key);
|
|
67
|
+
// A reader trims a value's leading whitespace as it trims any text's, so a cut part may begin past it.
|
|
68
|
+
const leadingForms = entries.map(([, value], index) => {
|
|
69
|
+
const key = valueKeys[index] ?? '';
|
|
70
|
+
return secretLength(value.trim()) >= MIN_SECRET_LENGTH && key.trimStart() !== key ? [key, key.trimStart()] : [key];
|
|
71
|
+
});
|
|
67
72
|
const redactCut = (text) => {
|
|
68
73
|
// Only what follows the last marker can end in a value cut short.
|
|
69
74
|
const tail = text.split(known).at(-1) ?? '';
|
|
70
|
-
const tailKey =
|
|
75
|
+
const tailKey = readingKey(tail);
|
|
71
76
|
let cut = 0;
|
|
72
77
|
let marker = '';
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
cut
|
|
77
|
-
|
|
78
|
+
leadingForms.forEach((forms, index) => {
|
|
79
|
+
for (const value of forms) {
|
|
80
|
+
const length = leadingPartAtEnd(tailKey.key, value, Math.min(FRAGMENT_LENGTH, Math.ceil(value.length / 2)));
|
|
81
|
+
if (length > cut) {
|
|
82
|
+
cut = length;
|
|
83
|
+
marker = markers[index] ?? '';
|
|
84
|
+
}
|
|
78
85
|
}
|
|
79
86
|
});
|
|
80
87
|
if (cut === 0)
|
|
81
88
|
return redact(text);
|
|
82
89
|
// An occurrence the whole-value pass would rewrite across the fragment's start joins the fragment.
|
|
83
|
-
let start =
|
|
90
|
+
let start = tailKey.at(tailKey.key.length - cut);
|
|
84
91
|
pattern.lastIndex = 0;
|
|
85
92
|
for (let match = pattern.exec(tail); match !== null && match.index < start; match = pattern.exec(tail)) {
|
|
86
93
|
if (match.index + match[0].length > start) {
|
|
@@ -221,6 +228,87 @@ function caseKey(text) {
|
|
|
221
228
|
out += ch.charCodeAt(0) < 128 ? ch.toLowerCase() : caseKeyOf(ch);
|
|
222
229
|
return out;
|
|
223
230
|
}
|
|
231
|
+
/** Whitespace a reader rewrites: a run of two or more, or one character other than a plain space. */
|
|
232
|
+
const COLLAPSIBLE = /\s\s|[^\S ]/u;
|
|
233
|
+
/** UTF-16 units joined into one string at a time when the key is rebuilt. */
|
|
234
|
+
const KEY_CHUNK = 8192;
|
|
235
|
+
/**
|
|
236
|
+
* `text` as a cut or a fragment of a value is compared: `caseKey`, with each
|
|
237
|
+
* whitespace run read as one space, as an engine that collapses whitespace
|
|
238
|
+
* (`normalizeText`) shows it before cutting. A value holding a line break, a
|
|
239
|
+
* tab, a CRLF, a no-break space, or a run of spaces then agrees with what a
|
|
240
|
+
* collapsed text keeps of it, and a stretch of the key maps back to the text
|
|
241
|
+
* through `at`. `caseKey` keeps every index, so text with nothing to collapse
|
|
242
|
+
* is its own key. Otherwise the key is rebuilt unit by unit into a typed
|
|
243
|
+
* array, and `at` keeps one pair of offsets per collapsed run: a trace text
|
|
244
|
+
* of many megabytes costs a few times its size, never an object per unit.
|
|
245
|
+
*/
|
|
246
|
+
function readingKey(text) {
|
|
247
|
+
const cased = caseKey(text);
|
|
248
|
+
if (!COLLAPSIBLE.test(cased))
|
|
249
|
+
return { key: cased, at: (index) => index };
|
|
250
|
+
const units = new Uint16Array(cased.length);
|
|
251
|
+
// Pairs of where a stretch after a collapsed run starts, in the key and in the text.
|
|
252
|
+
let breaks = new Int32Array(64);
|
|
253
|
+
let pairs = 0;
|
|
254
|
+
let length = 0;
|
|
255
|
+
for (let index = 0; index < cased.length; index += 1) {
|
|
256
|
+
const unit = cased.charCodeAt(index);
|
|
257
|
+
if (!isWhitespace(unit)) {
|
|
258
|
+
units[length++] = unit;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
let end = index + 1;
|
|
262
|
+
while (end < cased.length && isWhitespace(cased.charCodeAt(end)))
|
|
263
|
+
end += 1;
|
|
264
|
+
units[length++] = 0x20;
|
|
265
|
+
if (end - index > 1 || unit !== 0x20) {
|
|
266
|
+
if (2 * pairs + 2 > breaks.length) {
|
|
267
|
+
const grown = new Int32Array(breaks.length * 2);
|
|
268
|
+
grown.set(breaks);
|
|
269
|
+
breaks = grown;
|
|
270
|
+
}
|
|
271
|
+
breaks[2 * pairs] = length;
|
|
272
|
+
breaks[2 * pairs + 1] = end;
|
|
273
|
+
pairs += 1;
|
|
274
|
+
}
|
|
275
|
+
index = end - 1;
|
|
276
|
+
}
|
|
277
|
+
const chunks = [];
|
|
278
|
+
for (let start = 0; start < length; start += KEY_CHUNK) {
|
|
279
|
+
chunks.push(String.fromCharCode(...units.subarray(start, Math.min(length, start + KEY_CHUNK))));
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
key: chunks.join(''),
|
|
283
|
+
at: (index) => {
|
|
284
|
+
// The last stretch starting at or before `index`; before the first run the key and the text agree.
|
|
285
|
+
let low = -1;
|
|
286
|
+
let high = pairs - 1;
|
|
287
|
+
while (low < high) {
|
|
288
|
+
const middle = (low + high + 1) >> 1;
|
|
289
|
+
if ((breaks[2 * middle] ?? 0) <= index)
|
|
290
|
+
low = middle;
|
|
291
|
+
else
|
|
292
|
+
high = middle - 1;
|
|
293
|
+
}
|
|
294
|
+
return low === -1 ? index : (breaks[2 * low + 1] ?? 0) + index - (breaks[2 * low] ?? 0);
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/** Whether a UTF-16 unit is one `\s` matches: what `normalizeText` collapses. */
|
|
299
|
+
function isWhitespace(unit) {
|
|
300
|
+
return (unit === 0x20 ||
|
|
301
|
+
(unit >= 0x09 && unit <= 0x0d) ||
|
|
302
|
+
unit === 0xa0 ||
|
|
303
|
+
unit === 0x1680 ||
|
|
304
|
+
(unit >= 0x2000 && unit <= 0x200a) ||
|
|
305
|
+
unit === 0x2028 ||
|
|
306
|
+
unit === 0x2029 ||
|
|
307
|
+
unit === 0x202f ||
|
|
308
|
+
unit === 0x205f ||
|
|
309
|
+
unit === 0x3000 ||
|
|
310
|
+
unit === 0xfeff);
|
|
311
|
+
}
|
|
224
312
|
/** One non-ASCII character's `caseKey`, the same length as `ch`. */
|
|
225
313
|
function caseKeyOf(ch) {
|
|
226
314
|
const upper = sameLength(ch.toUpperCase(), ch);
|
|
@@ -306,8 +394,9 @@ function leadingPartAtEnd(text, value, minimum) {
|
|
|
306
394
|
/**
|
|
307
395
|
* Every run of `FRAGMENT_LENGTH` characters of every value, mapped to the
|
|
308
396
|
* index of the first value holding it; values come longest first, so a run
|
|
309
|
-
* two values share names the longer one. The values come as `
|
|
310
|
-
* reads them, so a run in any case
|
|
397
|
+
* two values share names the longer one. The values come as `readingKey`
|
|
398
|
+
* reads them, so a run in any case or with its whitespace collapsed finds
|
|
399
|
+
* its owner.
|
|
311
400
|
*/
|
|
312
401
|
function fragmentOwners(values) {
|
|
313
402
|
const owners = new Map();
|
|
@@ -323,29 +412,30 @@ function fragmentOwners(values) {
|
|
|
323
412
|
/**
|
|
324
413
|
* `text` with every stretch whose windows of `FRAGMENT_LENGTH` characters
|
|
325
414
|
* each occur in a value replaced by the marker of the value owning its first
|
|
326
|
-
* window. Windows are looked up by `
|
|
327
|
-
* fragment in another case
|
|
328
|
-
*
|
|
329
|
-
*
|
|
415
|
+
* window. Windows are looked up by `readingKey`, as `owners` is keyed, so a
|
|
416
|
+
* fragment in another case or with another whitespace run is one too, and
|
|
417
|
+
* the stretch is cut back out of `text` whole, its whitespace runs included.
|
|
418
|
+
* The stretch grows one window at a time, so the scan is linear in the text
|
|
419
|
+
* and a run spanning two values becomes one marker.
|
|
330
420
|
*/
|
|
331
421
|
function rewriteFragments(text, owners, markers) {
|
|
332
422
|
if (owners.size === 0)
|
|
333
423
|
return text;
|
|
334
|
-
const key =
|
|
424
|
+
const { key, at } = readingKey(text);
|
|
335
425
|
let out = '';
|
|
336
426
|
let kept = 0;
|
|
337
427
|
let start = 0;
|
|
338
|
-
while (start + FRAGMENT_LENGTH <=
|
|
428
|
+
while (start + FRAGMENT_LENGTH <= key.length) {
|
|
339
429
|
const owner = owners.get(key.slice(start, start + FRAGMENT_LENGTH));
|
|
340
430
|
if (owner === undefined) {
|
|
341
431
|
start += 1;
|
|
342
432
|
continue;
|
|
343
433
|
}
|
|
344
434
|
let end = start + FRAGMENT_LENGTH;
|
|
345
|
-
while (end <
|
|
435
|
+
while (end < key.length && owners.has(key.slice(end + 1 - FRAGMENT_LENGTH, end + 1)))
|
|
346
436
|
end += 1;
|
|
347
|
-
out += `${text.slice(kept, start)}${markers[owner] ?? ''}`;
|
|
348
|
-
kept = end;
|
|
437
|
+
out += `${text.slice(kept, at(start))}${markers[owner] ?? ''}`;
|
|
438
|
+
kept = at(end);
|
|
349
439
|
start = end;
|
|
350
440
|
}
|
|
351
441
|
return out + text.slice(kept);
|
|
@@ -449,9 +539,12 @@ export class SecretLedger {
|
|
|
449
539
|
* `FRAGMENT_LENGTH` characters, or half of a value shorter than twice that.
|
|
450
540
|
* A cut is one position, so a boundary this short seldom matches plain text
|
|
451
541
|
* by chance. Matched as the value is written, in any case (`caseKey`), as
|
|
452
|
-
* a CSS `text-transform` shows it
|
|
453
|
-
*
|
|
454
|
-
*
|
|
542
|
+
* a CSS `text-transform` shows it, and with every whitespace run on both
|
|
543
|
+
* sides read as one space (`readingKey`) and the value's leading whitespace
|
|
544
|
+
* dropped where whole-value matching trims it (`valuePattern`), as an
|
|
545
|
+
* engine that collapses text before cutting it shows it: the cut falls on
|
|
546
|
+
* text as the engine read it, before any serializer spells it. A case
|
|
547
|
+
* mapping that changes length (`ß` to `SS`) is not followed. The part is found before whole values are
|
|
455
548
|
* rewritten, so a value that starts with another registered value is not
|
|
456
549
|
* half rewritten as the shorter one, and an occurrence running into the
|
|
457
550
|
* part joins its marker. Bound like `redact`.
|
|
@@ -463,7 +556,7 @@ export class SecretLedger {
|
|
|
463
556
|
* becomes its marker. For a recording that keeps what an engine read raw
|
|
464
557
|
* (a Playwright trace holds the page's cut text and selections), where a
|
|
465
558
|
* value cut or selected partway through survives whole-value matching.
|
|
466
|
-
* Matched
|
|
559
|
+
* Matched in any case and with whitespace runs collapsed, like `redactCut`. A
|
|
467
560
|
* base64 or base64url run that decodes to text holding a value or a
|
|
468
561
|
* fragment is rewritten whole (a basic-auth header the engine sent). Bound
|
|
469
562
|
* like `redact`.
|
package/dist/internal/time.d.ts
CHANGED
|
@@ -30,6 +30,12 @@ export interface PollConditionOptions {
|
|
|
30
30
|
* Polls a condition until it holds (or, when negated, until its negation has
|
|
31
31
|
* held continuously for the negation grace window), throwing the caller's
|
|
32
32
|
* error at the deadline.
|
|
33
|
+
*
|
|
34
|
+
* A negation holds from the moment the read that first saw it was issued, so
|
|
35
|
+
* a slow read counts toward the window. A budget shorter than the window
|
|
36
|
+
* still has to be satisfiable: the negation then only needs to hold for the
|
|
37
|
+
* whole budget, and passes at the deadline on what it has seen, since a read
|
|
38
|
+
* past the deadline has no budget left.
|
|
33
39
|
*/
|
|
34
40
|
export declare function pollCondition(options: PollConditionOptions): Promise<void>;
|
|
35
41
|
/** Races a promise against a timeout; on timeout invokes onTimeout to build the error. */
|
package/dist/internal/time.js
CHANGED
|
@@ -47,29 +47,41 @@ export const NEGATION_GRACE_MS = 1000;
|
|
|
47
47
|
* Polls a condition until it holds (or, when negated, until its negation has
|
|
48
48
|
* held continuously for the negation grace window), throwing the caller's
|
|
49
49
|
* error at the deadline.
|
|
50
|
+
*
|
|
51
|
+
* A negation holds from the moment the read that first saw it was issued, so
|
|
52
|
+
* a slow read counts toward the window. A budget shorter than the window
|
|
53
|
+
* still has to be satisfiable: the negation then only needs to hold for the
|
|
54
|
+
* whole budget, and passes at the deadline on what it has seen, since a read
|
|
55
|
+
* past the deadline has no budget left.
|
|
50
56
|
*/
|
|
51
57
|
export async function pollCondition(options) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const grace = Math.min(NEGATION_GRACE_MS,
|
|
55
|
-
let
|
|
58
|
+
const { deadline, negated } = options;
|
|
59
|
+
const startedAt = Date.now();
|
|
60
|
+
const grace = Math.min(NEGATION_GRACE_MS, deadline.remaining(startedAt));
|
|
61
|
+
let readAt = startedAt;
|
|
62
|
+
let holdingSince;
|
|
63
|
+
const holds = (now) => holdingSince !== undefined && now - holdingSince >= grace;
|
|
56
64
|
for (;;) {
|
|
57
65
|
const value = await options.evaluate();
|
|
58
|
-
if (!
|
|
66
|
+
if (!negated) {
|
|
59
67
|
if (value === true)
|
|
60
68
|
return;
|
|
61
69
|
}
|
|
62
|
-
else if (value === false) {
|
|
63
|
-
negatedTrueSince ??= Date.now();
|
|
64
|
-
if (Date.now() - negatedTrueSince >= grace)
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
70
|
else {
|
|
68
|
-
|
|
71
|
+
holdingSince = value === false ? (holdingSince ?? readAt) : undefined;
|
|
69
72
|
}
|
|
70
|
-
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
if (holds(now))
|
|
75
|
+
return;
|
|
76
|
+
if (deadline.expired(now))
|
|
71
77
|
throw await options.onTimeout();
|
|
72
|
-
await sleep(POLL_INTERVAL_MS, options.signal);
|
|
78
|
+
await sleep(negated ? Math.min(POLL_INTERVAL_MS, deadline.remaining(now)) : POLL_INTERVAL_MS, options.signal);
|
|
79
|
+
readAt = Date.now();
|
|
80
|
+
if (negated && deadline.expired(readAt)) {
|
|
81
|
+
if (holds(readAt))
|
|
82
|
+
return;
|
|
83
|
+
throw await options.onTimeout();
|
|
84
|
+
}
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
/** Races a promise against a timeout; on timeout invokes onTimeout to build the error. */
|
package/dist/internal/urls.d.ts
CHANGED
|
@@ -23,10 +23,12 @@ export declare function isLoopbackHost(hostname: string): boolean;
|
|
|
23
23
|
/** True when environment may default to `test` (loopback, .localhost, .test hosts). */
|
|
24
24
|
export declare function isImplicitTestHost(hostname: string): boolean;
|
|
25
25
|
/**
|
|
26
|
-
* Resolves a navigation URL against the base and refuses
|
|
27
|
-
*
|
|
28
|
-
* a click can reach one just as well, so a
|
|
29
|
-
* would guard nothing.
|
|
26
|
+
* Resolves a navigation URL against the base and refuses every scheme but
|
|
27
|
+
* http(s), except the exact `about:blank`. Returns the absolute URL string.
|
|
28
|
+
* Any http(s) origin is admitted: a click can reach one just as well, so a
|
|
29
|
+
* gate on typed navigation alone would guard nothing. The rule is an
|
|
30
|
+
* allowlist because a browser wraps and nests schemes (`view-source:file:`,
|
|
31
|
+
* `blob:`, `filesystem:`), and a list of forbidden ones misses the wrapper.
|
|
30
32
|
*/
|
|
31
33
|
export declare function resolveNavigationUrl(input: string, base: NormalizedBaseUrl | undefined): {
|
|
32
34
|
url: string;
|
|
@@ -53,5 +55,7 @@ export declare function sameSite(url: string | URL, site: string): boolean;
|
|
|
53
55
|
* Relative expected strings resolve against the base URL; string comparison is
|
|
54
56
|
* exact after WHATWG serialization; regexps test the complete serialized URL.
|
|
55
57
|
* Anything else is `INVALID_ARGUMENT`, the `toTextPattern` rule.
|
|
58
|
+
* `ignoreCase` is Playwright's: `true` compares a string case-insensitively
|
|
59
|
+
* and adds the `i` flag to a regexp, `false` removes it.
|
|
56
60
|
*/
|
|
57
|
-
export declare function urlMatches(current: string, expected: string | RegExp, baseHref: string): boolean;
|
|
61
|
+
export declare function urlMatches(current: string, expected: string | RegExp, baseHref: string, ignoreCase?: boolean): boolean;
|
package/dist/internal/urls.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** URL normalization and origin policy helpers. */
|
|
2
2
|
import { ConfigurationError } from './errors.js';
|
|
3
3
|
import { testPattern } from './regexp.js';
|
|
4
|
-
import { toTextPattern } from './text.js';
|
|
4
|
+
import { toTextPattern, withIgnoreCase } from './text.js';
|
|
5
5
|
/**
|
|
6
6
|
* Parses and normalizes the app base URL. Rejects userinfo, query, and
|
|
7
7
|
* fragment. WHATWG parsing handles IDNA ASCII hosts, dot segments, and
|
|
@@ -94,12 +94,16 @@ export function isLoopbackHost(hostname) {
|
|
|
94
94
|
export function isImplicitTestHost(hostname) {
|
|
95
95
|
return isLoopbackHost(hostname) || hostname.endsWith('.test');
|
|
96
96
|
}
|
|
97
|
-
const
|
|
97
|
+
const NAVIGABLE_PROTOCOLS = new Set(['http:', 'https:']);
|
|
98
|
+
/** The one non-http(s) page navigation opens: empty, with no local or inline content. */
|
|
99
|
+
const BLANK_PAGE = 'about:blank';
|
|
98
100
|
/**
|
|
99
|
-
* Resolves a navigation URL against the base and refuses
|
|
100
|
-
*
|
|
101
|
-
* a click can reach one just as well, so a
|
|
102
|
-
* would guard nothing.
|
|
101
|
+
* Resolves a navigation URL against the base and refuses every scheme but
|
|
102
|
+
* http(s), except the exact `about:blank`. Returns the absolute URL string.
|
|
103
|
+
* Any http(s) origin is admitted: a click can reach one just as well, so a
|
|
104
|
+
* gate on typed navigation alone would guard nothing. The rule is an
|
|
105
|
+
* allowlist because a browser wraps and nests schemes (`view-source:file:`,
|
|
106
|
+
* `blob:`, `filesystem:`), and a list of forbidden ones misses the wrapper.
|
|
103
107
|
*/
|
|
104
108
|
export function resolveNavigationUrl(input, base) {
|
|
105
109
|
let url;
|
|
@@ -114,7 +118,7 @@ export function resolveNavigationUrl(input, base) {
|
|
|
114
118
|
}
|
|
115
119
|
throw new ConfigurationError('POLICY_DENIED', `malformed URL: ${input}`);
|
|
116
120
|
}
|
|
117
|
-
if (
|
|
121
|
+
if (!NAVIGABLE_PROTOCOLS.has(url.protocol) && url.href !== BLANK_PAGE) {
|
|
118
122
|
throw new ConfigurationError('POLICY_DENIED', `forbidden URL scheme: ${url.protocol}`);
|
|
119
123
|
}
|
|
120
124
|
return { url: url.href };
|
|
@@ -160,9 +164,11 @@ export function sameSite(url, site) {
|
|
|
160
164
|
* Relative expected strings resolve against the base URL; string comparison is
|
|
161
165
|
* exact after WHATWG serialization; regexps test the complete serialized URL.
|
|
162
166
|
* Anything else is `INVALID_ARGUMENT`, the `toTextPattern` rule.
|
|
167
|
+
* `ignoreCase` is Playwright's: `true` compares a string case-insensitively
|
|
168
|
+
* and adds the `i` flag to a regexp, `false` removes it.
|
|
163
169
|
*/
|
|
164
|
-
export function urlMatches(current, expected, baseHref) {
|
|
165
|
-
const pattern = toTextPattern(expected);
|
|
170
|
+
export function urlMatches(current, expected, baseHref, ignoreCase) {
|
|
171
|
+
const pattern = withIgnoreCase(toTextPattern(expected), ignoreCase);
|
|
166
172
|
if (pattern.kind === 'regexp') {
|
|
167
173
|
return testPattern(pattern.source, pattern.flags, serializeForComparison(current));
|
|
168
174
|
}
|
|
@@ -173,7 +179,10 @@ export function urlMatches(current, expected, baseHref) {
|
|
|
173
179
|
catch {
|
|
174
180
|
return false;
|
|
175
181
|
}
|
|
176
|
-
|
|
182
|
+
const actual = serializeForComparison(current);
|
|
183
|
+
return ignoreCase === true
|
|
184
|
+
? actual.toLowerCase() === expectedUrl.href.toLowerCase()
|
|
185
|
+
: actual === expectedUrl.href;
|
|
177
186
|
}
|
|
178
187
|
function serializeForComparison(url) {
|
|
179
188
|
try {
|
package/dist/locator/engine.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ export declare function attributeOf(node: SemanticNode, name: string): string |
|
|
|
18
18
|
* empty or absent.
|
|
19
19
|
*/
|
|
20
20
|
export declare function denySecureRead(nodes: readonly SemanticNode[], label: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* What a frame the engine reports missing means to a resolve: `wait` treats
|
|
23
|
+
* it like a node that is not there yet, `empty` answers zero matches.
|
|
24
|
+
*/
|
|
25
|
+
type MissingFrame = 'wait' | 'empty';
|
|
21
26
|
interface LocatorEngineOptions {
|
|
22
27
|
readonly session: TargetSession;
|
|
23
28
|
/** The running phase's signal and deadline, read per operation. */
|
|
@@ -95,8 +100,13 @@ export declare class LocatorEngine {
|
|
|
95
100
|
/** Reads every current match at once, for `allTextContents`: a missing frame reads as no matches. */
|
|
96
101
|
readAllNow(expression: LocatorExpression): Promise<readonly SemanticNode[]>;
|
|
97
102
|
private readEvery;
|
|
98
|
-
/**
|
|
99
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Reads one node for polling surfaces; returns null while zero matches.
|
|
105
|
+
* A poll that waits for absence passes `missingFrame: 'empty'`: nothing
|
|
106
|
+
* under a frame that is not in the document is attached, so the sample is
|
|
107
|
+
* zero matches rather than a wait for the frame.
|
|
108
|
+
*/
|
|
109
|
+
tryRead(expression: LocatorExpression, deadline: Deadline, missingFrame?: MissingFrame): Promise<{
|
|
100
110
|
node: SemanticNode | null;
|
|
101
111
|
count: number;
|
|
102
112
|
}>;
|
package/dist/locator/engine.js
CHANGED
|
@@ -213,9 +213,14 @@ export class LocatorEngine {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
|
-
/**
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Reads one node for polling surfaces; returns null while zero matches.
|
|
218
|
+
* A poll that waits for absence passes `missingFrame: 'empty'`: nothing
|
|
219
|
+
* under a frame that is not in the document is attached, so the sample is
|
|
220
|
+
* zero matches rather than a wait for the frame.
|
|
221
|
+
*/
|
|
222
|
+
async tryRead(expression, deadline, missingFrame = 'wait') {
|
|
223
|
+
return this.readOne(expression, deadline, missingFrame);
|
|
219
224
|
}
|
|
220
225
|
/**
|
|
221
226
|
* Reads the one current match at once, for `isVisible` and `isHidden`:
|
package/dist/locator/screen.js
CHANGED
|
@@ -55,6 +55,27 @@ const TEXT_OPTION_KEYS = ['exact', 'visible'];
|
|
|
55
55
|
const ROLE_FILTER_KEYS = [...TEXT_OPTION_KEYS, 'checked', 'disabled', 'selected', 'expanded', 'pressed', 'level'];
|
|
56
56
|
/** The keys of `RoleOptions`: the accessible name and the filters. */
|
|
57
57
|
const ROLE_OPTION_KEYS = ['name', ...ROLE_FILTER_KEYS];
|
|
58
|
+
/** The states `locator.waitFor` waits for, Playwright's four. */
|
|
59
|
+
const WAIT_FOR_STATES = ['attached', 'detached', 'visible', 'hidden'];
|
|
60
|
+
/** The states absence satisfies: a frame missing from the document reads as zero matches for them. */
|
|
61
|
+
const ABSENCE_STATES = new Set(['detached', 'hidden']);
|
|
62
|
+
/**
|
|
63
|
+
* Whether the one match, or its absence, is in `state`: `attached` is any
|
|
64
|
+
* match, `detached` none, `visible` a match that is not hidden, and `hidden`
|
|
65
|
+
* a hidden match or none.
|
|
66
|
+
*/
|
|
67
|
+
function inWaitForState(node, state) {
|
|
68
|
+
switch (state) {
|
|
69
|
+
case 'attached':
|
|
70
|
+
return node !== null;
|
|
71
|
+
case 'detached':
|
|
72
|
+
return node === null;
|
|
73
|
+
case 'visible':
|
|
74
|
+
return isNodeVisible(node);
|
|
75
|
+
case 'hidden':
|
|
76
|
+
return !isNodeVisible(node);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
58
79
|
class ScreenImpl {
|
|
59
80
|
context;
|
|
60
81
|
scope;
|
|
@@ -361,7 +382,11 @@ class LocatorImpl extends ScreenImpl {
|
|
|
361
382
|
return nodes.map((node) => normalizeText(node.text ?? ''));
|
|
362
383
|
}
|
|
363
384
|
async waitFor(options) {
|
|
364
|
-
|
|
385
|
+
rejectUnknownOptions('waitFor', options, ['state', 'timeout']);
|
|
386
|
+
const state = options?.state === undefined ? 'visible' : options.state;
|
|
387
|
+
if (!WAIT_FOR_STATES.includes(state)) {
|
|
388
|
+
throw new TestError('INVALID_ARGUMENT', `waitFor state must be one of ${WAIT_FOR_STATES.join(', ')}, got ${JSON.stringify(state)}`);
|
|
389
|
+
}
|
|
365
390
|
await this.context.steps.run('locator', 'locator.waitFor', `${this.label} → ${state}`, async () => {
|
|
366
391
|
const { engine } = this.context;
|
|
367
392
|
const deadline = engine.deadline(options?.timeout);
|
|
@@ -371,9 +396,8 @@ class LocatorImpl extends ScreenImpl {
|
|
|
371
396
|
signal: engine.signal,
|
|
372
397
|
negated: false,
|
|
373
398
|
evaluate: async () => {
|
|
374
|
-
const { node } = await engine.tryRead(this.expression, deadline);
|
|
375
|
-
|
|
376
|
-
return state === 'visible' ? visible : !visible;
|
|
399
|
+
const { node } = await engine.tryRead(this.expression, deadline, ABSENCE_STATES.has(state) ? 'empty' : 'wait');
|
|
400
|
+
return inWaitForState(node, state);
|
|
377
401
|
},
|
|
378
402
|
onTimeout: () => new TestError('LOCATOR_NOT_FOUND', `locator did not become ${state}: ${this.label}`, {
|
|
379
403
|
details: locatorDetails(this.expression, Date.now() - startedMs),
|
package/dist/mcp/catalog.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { ToolSet } from 'ai';
|
|
14
14
|
import type { StepExecutorContext } from '../agent/executor.ts';
|
|
15
|
+
import { type NodeRedaction } from '../agent/observation.ts';
|
|
15
16
|
import type { ScreenPresenter } from '../agent/screen-update.ts';
|
|
16
17
|
import type { LocatorExpression, SemanticNode, TargetSession } from '../engine/surface.ts';
|
|
17
18
|
import type { LocatorEngine } from '../locator/engine.ts';
|
|
@@ -35,7 +36,7 @@ export interface CatalogOptions {
|
|
|
35
36
|
/** The agent's project tools (`agents.<name>.tools`), served beside the built-in ones. */
|
|
36
37
|
readonly tools: Readonly<Record<string, AgentTool>>;
|
|
37
38
|
/** The attempt's secret ledger: what `locate` shows of a node passes through it, as `observe` does. */
|
|
38
|
-
readonly
|
|
39
|
+
readonly redaction: NodeRedaction;
|
|
39
40
|
/** The session's recorder, when the engine records video. */
|
|
40
41
|
readonly recorder: SessionRecorder | undefined;
|
|
41
42
|
readonly warn: (message: string) => void;
|
|
@@ -60,4 +61,4 @@ export interface LocateQuery {
|
|
|
60
61
|
/** Builds the expression and the matching `screen.*` call from the tool arguments. */
|
|
61
62
|
export declare function locateQuery(args: LocateArgs): LocateQuery;
|
|
62
63
|
/** Renders a locate result: the count, the verdict a test would get, and the nodes, each through the attempt's redactor. */
|
|
63
|
-
export declare function describeLocate(query: LocateQuery, count: number, nodes: readonly SemanticNode[],
|
|
64
|
+
export declare function describeLocate(query: LocateQuery, count: number, nodes: readonly SemanticNode[], redaction: NodeRedaction): string;
|
package/dist/mcp/catalog.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
import { projectTools } from '../agent/default-agent.js';
|
|
15
|
-
import { projectTree } from '../agent/observation.js';
|
|
15
|
+
import { projectTree, redactNode } from '../agent/observation.js';
|
|
16
16
|
import { GRAMMAR_TOOL_NAMES } from '../agent/action-names.js';
|
|
17
17
|
import { createGrammarTools } from '../agent/primitives.js';
|
|
18
18
|
import { ConfigurationError } from '../internal/errors.js';
|
|
@@ -34,7 +34,7 @@ export function createSessionCatalog(options) {
|
|
|
34
34
|
const builtIn = {
|
|
35
35
|
observe: fullObserveTool(context, screen),
|
|
36
36
|
...verbs,
|
|
37
|
-
locate: locateTool(options.locator, options.session, options.
|
|
37
|
+
locate: locateTool(options.locator, options.session, options.redaction),
|
|
38
38
|
...recording,
|
|
39
39
|
};
|
|
40
40
|
const defined = options.tools;
|
|
@@ -65,7 +65,7 @@ function fullObserveTool(context, screen) {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
/** The session's `locate`: a semantic locator tried against the live screen, with the verdict a test would get. */
|
|
68
|
-
function locateTool(locator, session,
|
|
68
|
+
function locateTool(locator, session, redaction) {
|
|
69
69
|
return {
|
|
70
70
|
description: 'Try a semantic locator against the live screen before writing it into a test: screen.getByRole(role, name), getByText, getByLabel, getByPlaceholder, or getByTestId. Returns how many nodes match and which, plus the test code to use. Exactly one of role, text, label, placeholder, or testId; name narrows a role query. Matching is exact unless exact is false.',
|
|
71
71
|
inputSchema: z.object({
|
|
@@ -84,7 +84,7 @@ function locateTool(locator, session, redact) {
|
|
|
84
84
|
for (const ref of refs.slice(0, MAX_LOCATE_NODES)) {
|
|
85
85
|
read.push(await session.read(ref, locator.operation()));
|
|
86
86
|
}
|
|
87
|
-
return describeLocate(query, refs.length, read,
|
|
87
|
+
return describeLocate(query, refs.length, read, redaction);
|
|
88
88
|
},
|
|
89
89
|
};
|
|
90
90
|
}
|
|
@@ -115,7 +115,7 @@ export function locateQuery(args) {
|
|
|
115
115
|
return { expression: textQuery('placeholder', placeholder, { exact }, undefined), code: `screen.getByPlaceholder(${JSON.stringify(placeholder)}${options})` };
|
|
116
116
|
}
|
|
117
117
|
/** Renders a locate result: the count, the verdict a test would get, and the nodes, each through the attempt's redactor. */
|
|
118
|
-
export function describeLocate(query, count, nodes,
|
|
118
|
+
export function describeLocate(query, count, nodes, redaction) {
|
|
119
119
|
const lines = [`${count === 1 ? '1 node matches' : `${count} nodes match`} ${describeExpression(query.expression)}.`];
|
|
120
120
|
if (count === 1)
|
|
121
121
|
lines.push(`Use: ${query.code}`);
|
|
@@ -124,7 +124,7 @@ export function describeLocate(query, count, nodes, redact) {
|
|
|
124
124
|
else
|
|
125
125
|
lines.push(`A test action on ${query.code} would fail with LOCATOR_AMBIGUOUS. Narrow it with a name, getByRole(role, name), .filter({ hasText }), .first(), or .nth(i), or scope it under a container.`);
|
|
126
126
|
for (const node of nodes)
|
|
127
|
-
lines.push(`- ${describeNode(projectTree(node,
|
|
127
|
+
lines.push(`- ${describeNode(projectTree(redactNode(node, redaction)))}`);
|
|
128
128
|
if (count > nodes.length)
|
|
129
129
|
lines.push(`- and ${count - nodes.length} more`);
|
|
130
130
|
return lines.join('\n');
|
package/dist/mcp/session.js
CHANGED
|
@@ -207,7 +207,7 @@ export class SessionHost {
|
|
|
207
207
|
screen,
|
|
208
208
|
session: attempt.session,
|
|
209
209
|
tools: config.agent.tools,
|
|
210
|
-
redact: attempt.agentRuntime.redact,
|
|
210
|
+
redaction: { redact: attempt.agentRuntime.redact, redactCut: attempt.agentRuntime.redactCut },
|
|
211
211
|
recorder,
|
|
212
212
|
locator: new LocatorEngine({
|
|
213
213
|
session: attempt.session,
|
package/dist/oauth/chatgpt.js
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
8
8
|
import { createOAuthFetch } from './fetch.js';
|
|
9
|
-
import { USER_AGENT
|
|
9
|
+
import { USER_AGENT } from '../internal/client-identity.js';
|
|
10
|
+
import { loginHint } from './providers.js';
|
|
10
11
|
import { createCodexProvider } from './providers/openai.js';
|
|
12
|
+
import { withoutServerStorage } from './responses.js';
|
|
11
13
|
import { defaultCredentialStore } from './store.js';
|
|
12
14
|
export function chatgpt(modelId) {
|
|
13
15
|
const fetch = createOAuthFetch(createCodexProvider(), {
|
|
@@ -18,25 +20,3 @@ export function chatgpt(modelId) {
|
|
|
18
20
|
// The key header is removed per request; the value only satisfies the constructor.
|
|
19
21
|
return withoutServerStorage(createOpenAI({ apiKey: 'oauth', fetch, name: 'chatgpt' }).responses(modelId));
|
|
20
22
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Tells the SDK what the Codex backend enforces: nothing is stored server
|
|
23
|
-
* side. Believing storage is on, the SDK refers back to an earlier turn's
|
|
24
|
-
* reasoning by id (`item_reference`), which the backend then cannot find; told
|
|
25
|
-
* it is off, the SDK carries the encrypted reasoning itself.
|
|
26
|
-
*/
|
|
27
|
-
function withoutServerStorage(model) {
|
|
28
|
-
const storeOff = (options) => ({
|
|
29
|
-
...options,
|
|
30
|
-
providerOptions: { ...options.providerOptions, openai: { ...options.providerOptions?.['openai'], store: false } },
|
|
31
|
-
});
|
|
32
|
-
return {
|
|
33
|
-
specificationVersion: model.specificationVersion,
|
|
34
|
-
provider: model.provider,
|
|
35
|
-
modelId: model.modelId,
|
|
36
|
-
get supportedUrls() {
|
|
37
|
-
return model.supportedUrls;
|
|
38
|
-
},
|
|
39
|
-
doGenerate: (options) => model.doGenerate(storeOff(options)),
|
|
40
|
-
doStream: (options) => model.doStream(storeOff(options)),
|
|
41
|
-
};
|
|
42
|
-
}
|
package/dist/oauth/cli.js
CHANGED
|
@@ -121,6 +121,7 @@ async function signedIn() {
|
|
|
121
121
|
const PROVIDER_HINTS = {
|
|
122
122
|
openai: 'ChatGPT Plus/Pro, the Codex sign-in',
|
|
123
123
|
'github-copilot': 'GitHub Copilot: OpenAI, Anthropic, Google, and SpaceXAI models',
|
|
124
|
+
'opencode-console': 'your OpenCode Console workspace',
|
|
124
125
|
spacexai: 'SuperGrok or X Premium+',
|
|
125
126
|
};
|
|
126
127
|
/** A terminal picker over providers; without a terminal the provider has to be named. */
|
package/dist/oauth/copilot.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `copilot('claude-sonnet-5')`: a GitHub Copilot subscription as an AI SDK
|
|
3
|
-
* model. Copilot serves
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* model. Copilot serves most of its models over the OpenAI chat protocol and
|
|
4
|
+
* some only over its Responses API; the instance asks the plan's model
|
|
5
|
+
* listing which on the first call and delegates to the matching model, so
|
|
6
|
+
* construction stays synchronous and touches the network only when a call
|
|
7
|
+
* does. An enterprise login stored by `e2e login` routes to its own host.
|
|
7
8
|
*/
|
|
8
9
|
import type { LanguageModelV4 } from '@ai-sdk/provider';
|
|
9
10
|
export declare function copilot(modelId: string): LanguageModelV4;
|