@volter/twin-standard 1.0.30 → 1.0.32

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.
Files changed (149) hide show
  1. package/dist/src/archive-source.d.ts +13 -0
  2. package/dist/src/archive-source.js +176 -0
  3. package/dist/src/check-sources.d.ts +19 -2
  4. package/dist/src/check-sources.js +95 -37
  5. package/dist/src/cli.js +52 -9
  6. package/dist/src/clients/ai-gateway.d.ts +1 -0
  7. package/dist/src/clients/ai-gateway.js +3 -0
  8. package/dist/src/clients/anthropic-messages.d.ts +10 -0
  9. package/dist/src/clients/anthropic-messages.js +52 -0
  10. package/dist/src/clients/anthropic.d.ts +1 -0
  11. package/dist/src/clients/anthropic.js +3 -0
  12. package/dist/src/clients/aws.d.ts +2 -0
  13. package/dist/src/clients/aws.js +137 -0
  14. package/dist/src/clients/cerebras.d.ts +1 -0
  15. package/dist/src/clients/cerebras.js +3 -0
  16. package/dist/src/clients/clerk.d.ts +2 -0
  17. package/dist/src/clients/clerk.js +99 -0
  18. package/dist/src/clients/cloudflare.d.ts +2 -0
  19. package/dist/src/clients/cloudflare.js +86 -0
  20. package/dist/src/clients/elevenlabs.d.ts +2 -0
  21. package/dist/src/clients/elevenlabs.js +39 -0
  22. package/dist/src/clients/gemini.d.ts +2 -0
  23. package/dist/src/clients/gemini.js +92 -0
  24. package/dist/src/clients/github.d.ts +2 -0
  25. package/dist/src/clients/github.js +71 -0
  26. package/dist/src/clients/groq.d.ts +1 -0
  27. package/dist/src/clients/groq.js +3 -0
  28. package/dist/src/clients/harness.d.ts +36 -0
  29. package/dist/src/clients/harness.js +74 -0
  30. package/dist/src/clients/merge.d.ts +1 -0
  31. package/dist/src/clients/merge.js +8 -0
  32. package/dist/src/clients/openai-compatible.d.ts +9 -0
  33. package/dist/src/clients/openai-compatible.js +51 -0
  34. package/dist/src/clients/openai.d.ts +2 -0
  35. package/dist/src/clients/openai.js +65 -0
  36. package/dist/src/clients/openrouter.d.ts +1 -0
  37. package/dist/src/clients/openrouter.js +3 -0
  38. package/dist/src/clients/perplexity.d.ts +1 -0
  39. package/dist/src/clients/perplexity.js +3 -0
  40. package/dist/src/clients/planetscale.d.ts +2 -0
  41. package/dist/src/clients/planetscale.js +109 -0
  42. package/dist/src/clients/posthog.d.ts +2 -0
  43. package/dist/src/clients/posthog.js +145 -0
  44. package/dist/src/clients/run.d.ts +1 -0
  45. package/dist/src/clients/run.js +25 -0
  46. package/dist/src/clients/sentry.d.ts +2 -0
  47. package/dist/src/clients/sentry.js +167 -0
  48. package/dist/src/clients/slack.d.ts +2 -0
  49. package/dist/src/clients/slack.js +107 -0
  50. package/dist/src/clients/stripe.d.ts +2 -0
  51. package/dist/src/clients/stripe.js +161 -0
  52. package/dist/src/clients/tinybird.d.ts +2 -0
  53. package/dist/src/clients/tinybird.js +71 -0
  54. package/dist/src/clients/tunnel.d.ts +2 -0
  55. package/dist/src/clients/tunnel.js +99 -0
  56. package/dist/src/clients/upstash.d.ts +2 -0
  57. package/dist/src/clients/upstash.js +143 -0
  58. package/dist/src/clients/volteridentity.d.ts +2 -0
  59. package/dist/src/clients/volteridentity.js +128 -0
  60. package/dist/src/clients/xai.d.ts +2 -0
  61. package/dist/src/clients/xai.js +23 -0
  62. package/dist/src/conformance.d.ts +33 -0
  63. package/dist/src/conformance.js +520 -0
  64. package/dist/src/derive.js +113 -22
  65. package/dist/src/gate.js +22 -16
  66. package/dist/src/index.d.ts +5 -3
  67. package/dist/src/index.js +5 -3
  68. package/dist/src/lanes.d.ts +2 -0
  69. package/dist/src/lanes.js +7 -0
  70. package/dist/src/p3-rules.js +0 -2
  71. package/dist/src/protocol-3.d.ts +1 -0
  72. package/dist/src/protocol-3.js +76 -30
  73. package/dist/src/published.d.ts +6 -0
  74. package/dist/src/published.js +65 -11
  75. package/dist/src/spec-documents.js +2 -1
  76. package/dist/src/spec-ir-client.d.ts +5 -1
  77. package/dist/src/spec-ir-client.js +12 -2
  78. package/dist/src/spec-ir.d.ts +7 -2
  79. package/dist/src/spec-ir.js +42 -10
  80. package/dist/src/verdict.d.ts +14 -0
  81. package/dist/src/verdict.js +30 -0
  82. package/dist/src/walk/behavior-journey.d.ts +47 -0
  83. package/dist/src/walk/behavior-journey.js +1138 -0
  84. package/dist/src/walk/git-client.d.ts +26 -0
  85. package/dist/src/walk/git-client.js +139 -0
  86. package/dist/src/walk/journey-kit.d.ts +445 -0
  87. package/dist/src/walk/journey-kit.js +743 -0
  88. package/dist/src/walk/shape.d.ts +54 -0
  89. package/dist/src/walk/shape.js +210 -0
  90. package/dist/src/walk/sse-example.d.ts +2 -0
  91. package/dist/src/walk/sse-example.js +32 -0
  92. package/dist/src/walk/vendor-fetch.d.ts +20 -0
  93. package/dist/src/walk/vendor-fetch.js +92 -0
  94. package/dist/src/walk/walk-database.d.ts +12 -0
  95. package/dist/src/walk/walk-database.js +96 -0
  96. package/dist/src/walk/xml-view.d.ts +2 -0
  97. package/dist/src/walk/xml-view.js +55 -0
  98. package/package.json +39 -3
  99. package/src/archive-source.ts +141 -0
  100. package/src/check-sources.ts +83 -33
  101. package/src/cli.ts +40 -8
  102. package/src/clients/ai-gateway.ts +4 -0
  103. package/src/clients/anthropic-messages.ts +60 -0
  104. package/src/clients/anthropic.ts +4 -0
  105. package/src/clients/aws.ts +119 -0
  106. package/src/clients/cerebras.ts +4 -0
  107. package/src/clients/clerk.ts +97 -0
  108. package/src/clients/cloudflare.ts +80 -0
  109. package/src/clients/elevenlabs.ts +38 -0
  110. package/src/clients/gemini.ts +89 -0
  111. package/src/clients/github.ts +67 -0
  112. package/src/clients/groq.ts +4 -0
  113. package/src/clients/harness.ts +101 -0
  114. package/src/clients/merge.ts +9 -0
  115. package/src/clients/openai-compatible.ts +59 -0
  116. package/src/clients/openai.ts +66 -0
  117. package/src/clients/openrouter.ts +4 -0
  118. package/src/clients/perplexity.ts +4 -0
  119. package/src/clients/planetscale.ts +98 -0
  120. package/src/clients/posthog.ts +132 -0
  121. package/src/clients/run.ts +18 -0
  122. package/src/clients/sentry.ts +157 -0
  123. package/src/clients/slack.ts +98 -0
  124. package/src/clients/stripe.ts +129 -0
  125. package/src/clients/tinybird.ts +69 -0
  126. package/src/clients/tunnel.ts +79 -0
  127. package/src/clients/upstash.ts +137 -0
  128. package/src/clients/volteridentity.ts +108 -0
  129. package/src/clients/xai.ts +25 -0
  130. package/src/conformance.ts +446 -0
  131. package/src/derive.ts +87 -16
  132. package/src/gate.ts +20 -14
  133. package/src/index.ts +5 -3
  134. package/src/lanes.ts +7 -0
  135. package/src/p3-rules.ts +0 -2
  136. package/src/protocol-3.ts +71 -27
  137. package/src/published.ts +59 -11
  138. package/src/spec-documents.ts +2 -1
  139. package/src/spec-ir-client.ts +15 -4
  140. package/src/spec-ir.ts +43 -5
  141. package/src/verdict.ts +28 -0
  142. package/src/walk/behavior-journey.ts +806 -0
  143. package/src/walk/git-client.ts +138 -0
  144. package/src/walk/journey-kit.ts +858 -0
  145. package/src/walk/shape.ts +208 -0
  146. package/src/walk/sse-example.ts +25 -0
  147. package/src/walk/vendor-fetch.ts +80 -0
  148. package/src/walk/walk-database.ts +97 -0
  149. package/src/walk/xml-view.ts +44 -0
@@ -0,0 +1,119 @@
1
+ // AWS's own clients, driving the aws pack as its applications do: @aws-sdk/client-s3 (as LibreChat, Twenty, Rallly and the
2
+ // platform's backups store their files) and @aws-sdk/client-secrets-manager, unmodified, with the access key the credential
3
+ // door issues, in us-east-1, each addressing AWS's own endpoints (S3's virtual-hosted buckets, Secrets Manager's Regional
4
+ // host) through an HTTPS agent as a World routes them. The S3 client computes and checks its default checksums as it does
5
+ // against AWS (a CRC32 sent with each upload, and checked on each download that answers one).
6
+ import type { ClientCase, ClientContext } from './harness.ts';
7
+
8
+ const PUT = 'https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html';
9
+ const COMPLETE = 'https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html';
10
+ const SECRET = 'https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html';
11
+
12
+ const options = (c: ClientContext) => ({
13
+ region: 'us-east-1',
14
+ credentials: { accessKeyId: c.env.AWS_ACCESS_KEY_ID ?? '', secretAccessKey: c.env.AWS_SECRET_ACCESS_KEY ?? '' },
15
+ requestHandler: { httpsAgent: c.agent, requestTimeout: 10_000 },
16
+ maxAttempts: 1,
17
+ });
18
+
19
+ const failed = (what: string) => (e: unknown): Error => new Error(`${what} failed: ${(e as Error).name}: ${(e as Error).message}`);
20
+
21
+ export const cases: ClientCase[] = [
22
+ {
23
+ id: 's3-object-round-trip',
24
+ asks: 'an object put with its type and metadata into a bucket the client made is found by HeadObject with them, listed under its prefix, and read back byte for byte, the client checking the CRC32 it sent',
25
+ source: `${PUT} "Adds an object to a bucket."`,
26
+ async run(c) {
27
+ const s3 = await import('@aws-sdk/client-s3');
28
+ const client = new s3.S3Client(options(c));
29
+ const body = new TextEncoder().encode('{"madeAt":"2026-01-06T02:00:00.000Z","files":{}}');
30
+ try {
31
+ await client.send(new s3.CreateBucketCommand({ Bucket: 'client-case-backups' })).catch((e: unknown) => { throw failed('CreateBucket')(e); });
32
+ await client.send(new s3.PutObjectCommand({ Bucket: 'client-case-backups', Key: 'platform-state/one.json', Body: body, ContentType: 'application/json', Metadata: { source: 'client-case' } }))
33
+ .catch((e: unknown) => { throw failed('PutObject')(e); });
34
+ const out: string[] = [];
35
+ const head = await client.send(new s3.HeadObjectCommand({ Bucket: 'client-case-backups', Key: 'platform-state/one.json' })).catch((e: unknown) => { throw failed('HeadObject')(e); });
36
+ if (head.ContentType !== 'application/json') out.push(`HeadObject answered the type ${String(head.ContentType)}, not the one put`);
37
+ if (head.Metadata?.source !== 'client-case') out.push('HeadObject answered no x-amz-meta-source');
38
+ if (head.ContentLength !== body.length) out.push(`HeadObject answered ${String(head.ContentLength)} bytes, not ${body.length}`);
39
+ const listed = await client.send(new s3.ListObjectsV2Command({ Bucket: 'client-case-backups', Prefix: 'platform-state/' })).catch((e: unknown) => { throw failed('ListObjectsV2')(e); });
40
+ if (!(listed.Contents ?? []).some((o) => o.Key === 'platform-state/one.json')) out.push('ListObjectsV2 does not list the object put under its prefix');
41
+ const got = await client.send(new s3.GetObjectCommand({ Bucket: 'client-case-backups', Key: 'platform-state/one.json' })).catch((e: unknown) => { throw failed('GetObject')(e); });
42
+ const back = await got.Body!.transformToByteArray().catch((e: unknown) => { throw failed('reading GetObject\'s body')(e); });
43
+ if (back.length !== body.length || back.some((b, i) => b !== body[i])) out.push(`GetObject answered ${back.length} bytes, not the ${body.length} put`);
44
+ return out;
45
+ } catch (e) { return [(e as Error).message]; }
46
+ },
47
+ },
48
+ {
49
+ id: 's3-multipart-upload',
50
+ asks: 'an upload of two parts, the first 5 MiB, completes into one object whose ETag names its two parts and whose bytes are the parts in order',
51
+ source: `${COMPLETE} "Completes a multipart upload by assembling previously uploaded parts."`,
52
+ async run(c) {
53
+ const s3 = await import('@aws-sdk/client-s3');
54
+ const client = new s3.S3Client(options(c));
55
+ const first = new Uint8Array(5 * 1024 * 1024).fill(97);
56
+ const second = new TextEncoder().encode('the recording\'s last bytes');
57
+ try {
58
+ await client.send(new s3.CreateBucketCommand({ Bucket: 'client-case-recordings' })).catch((e: unknown) => { throw failed('CreateBucket')(e); });
59
+ const made = await client.send(new s3.CreateMultipartUploadCommand({ Bucket: 'client-case-recordings', Key: 'calls/call.m4a', ContentType: 'audio/mp4' }))
60
+ .catch((e: unknown) => { throw failed('CreateMultipartUpload')(e); });
61
+ const UploadId = made.UploadId;
62
+ if (!UploadId) return ['CreateMultipartUpload answered no UploadId'];
63
+ const one = await client.send(new s3.UploadPartCommand({ Bucket: 'client-case-recordings', Key: 'calls/call.m4a', UploadId, PartNumber: 1, Body: first })).catch((e: unknown) => { throw failed('UploadPart 1')(e); });
64
+ const two = await client.send(new s3.UploadPartCommand({ Bucket: 'client-case-recordings', Key: 'calls/call.m4a', UploadId, PartNumber: 2, Body: second })).catch((e: unknown) => { throw failed('UploadPart 2')(e); });
65
+ const done = await client.send(new s3.CompleteMultipartUploadCommand({ Bucket: 'client-case-recordings', Key: 'calls/call.m4a', UploadId,
66
+ MultipartUpload: { Parts: [{ PartNumber: 1, ETag: one.ETag }, { PartNumber: 2, ETag: two.ETag }] } })).catch((e: unknown) => { throw failed('CompleteMultipartUpload')(e); });
67
+ const out: string[] = [];
68
+ if (!/-2"$/.test(String(done.ETag))) out.push(`the completed object's ETag ${String(done.ETag)} does not name its two parts`);
69
+ const got = await client.send(new s3.GetObjectCommand({ Bucket: 'client-case-recordings', Key: 'calls/call.m4a' })).catch((e: unknown) => { throw failed('GetObject')(e); });
70
+ const back = await got.Body!.transformToByteArray();
71
+ if (back.length !== first.length + second.length || back[0] !== 97 || back[back.length - 1] !== second[second.length - 1]) out.push(`GetObject answered ${back.length} bytes, not the parts joined (${first.length + second.length})`);
72
+ if (got.ContentType !== 'audio/mp4') out.push(`the object's type is ${String(got.ContentType)}, not the one its upload began with`);
73
+ const left = await client.send(new s3.ListMultipartUploadsCommand({ Bucket: 'client-case-recordings' })).catch((e: unknown) => { throw failed('ListMultipartUploads')(e); });
74
+ if ((left.Uploads ?? []).some((u) => u.UploadId === UploadId)) out.push('the completed upload is still listed in progress');
75
+ return out;
76
+ } catch (e) { return [(e as Error).message]; }
77
+ },
78
+ },
79
+ {
80
+ id: 'secrets-manager-create-and-read',
81
+ asks: 'a secret stored with its value is read back as the current version, labelled AWSCURRENT',
82
+ source: `${SECRET} "Retrieves the contents of the encrypted fields SecretString or SecretBinary from the specified version of a secret"`,
83
+ async run(c) {
84
+ const sm = await import('@aws-sdk/client-secrets-manager');
85
+ const client = new sm.SecretsManagerClient(options(c));
86
+ const SecretString = '{"username":"api","password":"client-case-password"}';
87
+ try {
88
+ const made = await client.send(new sm.CreateSecretCommand({ Name: 'client-case/db', SecretString })).catch((e: unknown) => { throw failed('CreateSecret')(e); });
89
+ const got = await client.send(new sm.GetSecretValueCommand({ SecretId: 'client-case/db' })).catch((e: unknown) => { throw failed('GetSecretValue')(e); });
90
+ const out: string[] = [];
91
+ if (got.SecretString !== SecretString) out.push('GetSecretValue answered another value than the one stored');
92
+ if (got.VersionId !== made.VersionId) out.push(`GetSecretValue answered version ${String(got.VersionId)}, not the ${String(made.VersionId)} CreateSecret made`);
93
+ if (!(got.VersionStages ?? []).includes('AWSCURRENT')) out.push('the version read is not labelled AWSCURRENT');
94
+ if (!String(made.ARN).startsWith('arn:aws:secretsmanager:us-east-1:')) out.push(`CreateSecret answered the ARN ${String(made.ARN)}, not one in us-east-1, the Region it was called in`);
95
+ return out;
96
+ } catch (e) { return [(e as Error).message]; }
97
+ },
98
+ },
99
+ {
100
+ id: 's3-backup-bucket-created-when-missing',
101
+ asks: 'HeadBucket rejects with HTTP 404 for a missing backup bucket; the backup job catches it and creates the bucket, which then answers HTTP 200',
102
+ source: 'https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html "The specified bucket does not exist."',
103
+ async run(c) {
104
+ const s3 = await import('@aws-sdk/client-s3');
105
+ const client = new s3.S3Client({ ...options(c), forcePathStyle: true });
106
+ const Bucket = 'client-case-backup-new-bucket';
107
+ try {
108
+ const missing = await client.send(new s3.HeadBucketCommand({ Bucket })).then(() => undefined,
109
+ (e: unknown) => e as { $metadata?: { httpStatusCode?: number } });
110
+ if (missing?.$metadata?.httpStatusCode !== 404) return ['HeadBucket did not reject the missing bucket with HTTP 404'];
111
+ // apps/platform/src/backup.ts catches the SDK rejection before creating the bucket.
112
+ await client.send(new s3.CreateBucketCommand({ Bucket }));
113
+ const found = await client.send(new s3.HeadBucketCommand({ Bucket }));
114
+ return found.$metadata.httpStatusCode === 200 ? [] : ['HeadBucket did not find the newly created backup bucket'];
115
+ } catch (e) { return [(e as Error).message]; }
116
+ finally { client.destroy(); }
117
+ },
118
+ },
119
+ ];
@@ -0,0 +1,4 @@
1
+ // Cerebras's chat through openai-node (its OpenAI-compatible API), the shared chat cases.
2
+ import { chatCases } from './openai-compatible.ts';
3
+
4
+ export const cases = chatCases({ key: 'CEREBRAS_API_KEY', base: (host) => `https://${host}/v1`, model: 'test-model' });
@@ -0,0 +1,97 @@
1
+ // Official @clerk/backend 2.33.3: runhuman pins 2.30.1 (through @clerk/fastify 2.6.19, its pnpm-lock.yaml), and 2.33.3 is
2
+ // that line's fix for GHSA-w24r-5266-9c3c, which the release scan refuses; the case drives it as
3
+ // runhuman's API server uses it: createClerkClient({ secretKey }), the paginated membership lists it reads roles and
4
+ // permissions from, the error's status it branches on, and the session token its Fastify plugin verifies.
5
+ // The SDK binds the global fetch when it is first imported, so the World's routed fetch is set before that import.
6
+ import type { ClientCase, ClientContext } from './harness.ts';
7
+
8
+ const ORG_MEMBERSHIPS = 'https://clerk.com/docs/references/backend/organization/get-organization-membership-list';
9
+ const USER_MEMBERSHIPS = 'https://clerk.com/docs/references/backend/user/get-organization-membership-list';
10
+ const OVERVIEW = 'https://clerk.com/docs/references/backend/overview';
11
+ const VERIFY = 'https://clerk.com/docs/references/backend/verify-token';
12
+
13
+ async function sdk(c: ClientContext) {
14
+ globalThis.fetch = c.fetch;
15
+ return await import('@clerk/backend');
16
+ }
17
+ async function client(c: ClientContext) {
18
+ const { createClerkClient } = await sdk(c);
19
+ return createClerkClient({ secretKey: c.env.CLERK_SECRET_KEY });
20
+ }
21
+ /** A person and an organization they created (Organizations turned on first, as an application's instance has them). */
22
+ async function founded(c: ClientContext, who: string) {
23
+ const clerk = await client(c);
24
+ await clerk.instance.updateOrganizationSettings({ enabled: true });
25
+ const user = await clerk.users.createUser({ emailAddress: [`${who}@sdk-case.example`], firstName: who, skipPasswordRequirement: true });
26
+ const org = await clerk.organizations.createOrganization({ name: `${who}'s team`, createdBy: user.id });
27
+ return { clerk, user, org };
28
+ }
29
+
30
+ export const cases: ClientCase[] = [
31
+ {
32
+ id: 'organization-membership-list-paginated',
33
+ asks: "the organization's memberships come back as { data, totalCount }, each membership's public user data and creation time read as the SDK's objects",
34
+ source: `${ORG_MEMBERSHIPS} "Returns a PaginatedResourceResponse object with a data property containing an array of OrganizationMembership objects and a totalCount property"`,
35
+ async run(c) {
36
+ const { clerk, user, org } = await founded(c, 'marta');
37
+ const { data, totalCount } = await clerk.organizations.getOrganizationMembershipList({ organizationId: org.id, limit: 100 });
38
+ const failures: string[] = [];
39
+ if (totalCount !== 1 || data.length !== 1) failures.push(`the creator's organization lists ${data.length} memberships of ${totalCount}, not its one`);
40
+ const m = data[0];
41
+ if (m?.publicUserData?.userId !== user.id) failures.push('the membership names no publicUserData.userId of its member');
42
+ if (m?.publicUserData?.identifier !== 'marta@sdk-case.example') failures.push('the membership lost its member\'s identifier');
43
+ if (typeof m?.createdAt !== 'number' || Number.isNaN(new Date(m.createdAt).getTime())) failures.push('the membership\'s createdAt is no time');
44
+ return failures;
45
+ },
46
+ },
47
+ {
48
+ id: 'user-membership-list-names-organization-and-permissions',
49
+ asks: "a user's memberships come back as { data, totalCount }, each naming its organization and the permissions its role grants",
50
+ source: `${USER_MEMBERSHIPS} "Returns a PaginatedResourceResponse object with a data property that contains an array of OrganizationMembership objects"`,
51
+ async run(c) {
52
+ const { clerk, user, org } = await founded(c, 'rui');
53
+ const { data, totalCount } = await clerk.users.getOrganizationMembershipList({ userId: user.id, limit: 100 });
54
+ const failures: string[] = [];
55
+ const m = data.find((x) => x.organization?.id === org.id);
56
+ if (totalCount !== 1 || !m) failures.push(`the creator's memberships (${totalCount}) do not name the organization made`);
57
+ else if (!Array.isArray(m.permissions) || !m.permissions.length) failures.push(`the creator's membership (${m.role}) carries no permissions`);
58
+ return failures;
59
+ },
60
+ },
61
+ {
62
+ id: 'api-error-carries-status-and-code',
63
+ asks: "a refused call throws ClerkAPIResponseError carrying the HTTP status and the errors Clerk answered: an unknown user's memberships are a 404, which runhuman's isMember reads as no membership (packages/api/src/services/clerk/clerk-org.api.ts:51-63)",
64
+ source: `${OVERVIEW} "The clerkClient methods throw errors ( ClerkAPIResponseError ) when something goes wrong."`,
65
+ async run(c) {
66
+ const clerk = await client(c);
67
+ await clerk.instance.updateOrganizationSettings({ enabled: true });
68
+ try {
69
+ await clerk.users.getOrganizationMembershipList({ userId: 'user_2missing0000000000000000', limit: 100 });
70
+ return ["an unknown user's memberships were answered"];
71
+ } catch (e) {
72
+ const error = e as { status?: number; errors?: Array<{ code?: string }> };
73
+ const failures: string[] = [];
74
+ if (error.status !== 404) failures.push(`the error's status is ${error.status}, not 404`);
75
+ if (error.errors?.[0]?.code !== 'resource_not_found') failures.push(`the error's first code is ${error.errors?.[0]?.code}, not resource_not_found`);
76
+ return failures;
77
+ }
78
+ },
79
+ },
80
+ {
81
+ id: 'session-token-verified-against-jwks',
82
+ asks: "a session's token verifies with the secret key alone, the SDK fetching the instance's JWKS from the Backend API",
83
+ source: `${VERIFY} "Otherwise, performs a network call to retrieve the JWKS from the Backend API"`,
84
+ async run(c) {
85
+ const { verifyToken } = await sdk(c);
86
+ const clerk = await client(c);
87
+ const user = await clerk.users.createUser({ emailAddress: ['ines@sdk-case.example'], skipPasswordRequirement: true });
88
+ const session = await clerk.sessions.createSession({ userId: user.id });
89
+ const { jwt } = await clerk.sessions.getToken(session.id);
90
+ const payload = await verifyToken(jwt, { secretKey: c.env.CLERK_SECRET_KEY });
91
+ const failures: string[] = [];
92
+ if (payload.sub !== user.id) failures.push(`the verified token's sub is ${String(payload.sub)}, not the user`);
93
+ if (payload.sid !== session.id) failures.push(`the verified token's sid is ${String(payload.sid)}, not the session`);
94
+ return failures;
95
+ },
96
+ },
97
+ ];
@@ -0,0 +1,80 @@
1
+ // Cloudflare's own clients, driving the cloudflare pack as its applications do: the official TypeScript library
2
+ // (`cloudflare`, as Twenty calls custom hostnames and an operator makes a Turnstile widget) with the account token the
3
+ // credential door issues, and aws4fetch (as RH2 and Dub reach R2's S3 API) with the R2 keys it issues, signing for the
4
+ // account's own R2 endpoint.
5
+ import type { ClientCase, ClientContext } from './harness.ts';
6
+
7
+ const HOSTNAMES = 'https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/hostname-validation/';
8
+ const WIDGETS = 'https://developers.cloudflare.com/turnstile/get-started/widget-management/api/';
9
+ const AWS4FETCH = 'https://developers.cloudflare.com/r2/examples/aws/aws4fetch/';
10
+
11
+ async function cloudflare(c: ClientContext) {
12
+ const { default: Cloudflare } = await import('cloudflare');
13
+ return new Cloudflare({ apiToken: c.env.CLOUDFLARE_API_TOKEN ?? '', fetch: c.fetch, maxRetries: 0, timeout: 10_000 });
14
+ }
15
+
16
+ const failed = (what: string) => (e: unknown): Error => new Error(`${what} failed: ${(e as Error).message}`);
17
+
18
+ export const cases: ClientCase[] = [
19
+ {
20
+ id: 'custom-hostname-pending-and-found',
21
+ asks: 'a custom hostname registered on a zone is pending, with the ownership verification its customer publishes, and is found again by its hostname',
22
+ source: `${HOSTNAMES} "validate hostname ownership and affect the custom hostname"`,
23
+ async run(c) {
24
+ const cf = await cloudflare(c);
25
+ const account = c.env.CLOUDFLARE_ACCOUNT_ID ?? '';
26
+ try {
27
+ const zone = await cf.zones.create({ account: { id: account }, name: 'client-case.example', type: 'full' }).catch((e: unknown) => { throw failed('zones.create')(e); });
28
+ const made = await cf.customHostnames.create({ zone_id: zone.id, hostname: 'shop.customer-case.example', ssl: { method: 'txt', type: 'dv' } })
29
+ .catch((e: unknown) => { throw failed('customHostnames.create')(e); });
30
+ const out: string[] = [];
31
+ if (made.status !== 'pending') out.push(`a new custom hostname is ${String(made.status)}, not pending`);
32
+ if (!made.ownership_verification?.name || !made.ownership_verification?.value) out.push('a new custom hostname names no ownership verification record');
33
+ const page = await cf.customHostnames.list({ zone_id: zone.id, hostname: 'shop.customer-case.example' as never }).catch((e: unknown) => { throw failed('customHostnames.list')(e); });
34
+ const found = page.getPaginatedItems();
35
+ if (found.length !== 1 || found[0]?.id !== made.id) out.push(`the hostname filter found ${found.length} hostname(s), not the one made`);
36
+ return out;
37
+ } catch (e) { return [(e as Error).message]; }
38
+ },
39
+ },
40
+ {
41
+ id: 'turnstile-widget-secret-at-detail',
42
+ asks: "a widget made through the API answers its sitekey and secret, and its detail answers the same secret",
43
+ source: `${WIDGETS} "Turnstile Widget Details"`,
44
+ async run(c) {
45
+ const cf = await cloudflare(c);
46
+ const account_id = c.env.CLOUDFLARE_ACCOUNT_ID ?? '';
47
+ try {
48
+ const widget = await cf.turnstile.widgets.create({ account_id, domains: ['client-case.example'], mode: 'managed', name: 'client case sign-up' })
49
+ .catch((e: unknown) => { throw failed('turnstile.widgets.create')(e); });
50
+ if (!widget.sitekey || !widget.secret) return ['the new widget answers no sitekey or no secret'];
51
+ const detail = await cf.turnstile.widgets.get(widget.sitekey, { account_id }).catch((e: unknown) => { throw failed('turnstile.widgets.get')(e); });
52
+ return detail.secret === widget.secret ? [] : ['the widget\'s detail answers another secret than its creation'];
53
+ } catch (e) { return [(e as Error).message]; }
54
+ },
55
+ },
56
+ {
57
+ id: 'r2-object-round-trip-aws4fetch',
58
+ asks: "an object put through aws4fetch at the account's R2 endpoint is listed by ListObjectsV2 and read back byte for byte",
59
+ source: `${AWS4FETCH} "ListObjectsV2Result"`,
60
+ async run(c) {
61
+ const { AwsClient } = await import('aws4fetch');
62
+ const r2 = new AwsClient({ accessKeyId: c.env.R2_ACCESS_KEY_ID ?? '', secretAccessKey: c.env.R2_SECRET_ACCESS_KEY ?? '', service: 's3', region: 'auto' });
63
+ const base = c.env.R2_ENDPOINT ?? '';
64
+ const send = async (url: string, init?: RequestInit): Promise<Response> => c.fetch(await r2.sign(url, init));
65
+ const bytes = new TextEncoder().encode('a recording, as RH2 imports it\n');
66
+ const made = await send(`${base}/client-case-recordings`, { method: 'PUT' });
67
+ if (!made.ok) return [`CreateBucket answered ${made.status}`];
68
+ const put = await send(`${base}/client-case-recordings/rooms/r1.txt`, { method: 'PUT', body: bytes, headers: { 'content-type': 'text/plain' } });
69
+ if (!put.ok) return [`PutObject answered ${put.status}`];
70
+ const out: string[] = [];
71
+ const listed = await (await send(`${base}/client-case-recordings?list-type=2`)).text();
72
+ if (!listed.includes('<Key>rooms/r1.txt</Key>')) out.push('ListObjectsV2 does not list the object put');
73
+ const got = await send(`${base}/client-case-recordings/rooms/r1.txt`);
74
+ const back = new Uint8Array(await got.arrayBuffer());
75
+ if (got.status !== 200 || back.length !== bytes.length || back.some((b, i) => b !== bytes[i])) out.push(`GetObject answered ${got.status} with ${back.length} bytes, not the ${bytes.length} put`);
76
+ if (got.headers.get('content-type') !== 'text/plain') out.push(`GetObject answered the type ${String(got.headers.get('content-type'))}, not the one put`);
77
+ return out;
78
+ },
79
+ },
80
+ ];
@@ -0,0 +1,38 @@
1
+ // ElevenLabs' own client (@elevenlabs/elevenlabs-js), driving the elevenlabs pack as Volter Harness's voice does: an agent
2
+ // made, a signed URL asked for, and the conversation's WebSocket opened at it (the ws library, as a Node application opens
3
+ // it), its first event the one ElevenLabs documents.
4
+ import type { ClientCase, ClientContext } from './harness.ts';
5
+
6
+ const SOCKET = 'https://elevenlabs.io/docs/agents-platform/api-reference/agents-platform/websocket';
7
+
8
+ async function client(c: ClientContext) {
9
+ const { ElevenLabsClient } = await import('@elevenlabs/elevenlabs-js');
10
+ return new ElevenLabsClient({ apiKey: c.env.ELEVENLABS_API_KEY ?? '', baseUrl: `https://${c.host}`, fetch: c.fetch, maxRetries: 0, timeoutInSeconds: 10 });
11
+ }
12
+
13
+ export const cases: ClientCase[] = [
14
+ {
15
+ id: 'agent-conversation-opens',
16
+ asks: "an agent's conversation opened at its signed URL answers the client's initiation data with conversation_initiation_metadata",
17
+ source: `${SOCKET} "conversation_initiation_client_data"`,
18
+ async run(c) {
19
+ const el = await client(c);
20
+ const agent = await el.conversationalAi.agents.create({ name: 'Conformance', conversationConfig: { agent: { firstMessage: 'Hello.', prompt: { prompt: 'Answer briefly.' } } } } as never).catch((e: unknown) => e as Error);
21
+ if (agent instanceof Error) return [`agents.create failed: ${agent.message}`];
22
+ const signed = await el.conversationalAi.conversations.getSignedUrl({ agentId: (agent as { agentId: string }).agentId }).catch((e: unknown) => e as Error);
23
+ if (signed instanceof Error) return [`conversations.getSignedUrl failed: ${signed.message}`];
24
+ const url = (signed as { signedUrl: string }).signedUrl;
25
+ if (!url.startsWith('wss://')) return [`the signed URL is not ElevenLabs' secure socket: ${url.slice(0, 60)}`];
26
+ const { WebSocket } = await import('ws');
27
+ const first = await new Promise<string>((done) => {
28
+ const ws = new WebSocket(url, { createConnection: c.connect } as never);
29
+ const timer = setTimeout(() => { ws.close(); done('no event in 10 s'); }, 10_000);
30
+ // the client's first frame, as ElevenLabs' SDKs send it: its initiation data
31
+ ws.on('open', () => ws.send(JSON.stringify({ type: 'conversation_initiation_client_data' })));
32
+ ws.on('message', (d: Buffer) => { clearTimeout(timer); ws.close(); try { done(String((JSON.parse(String(d)) as { type?: string }).type)); } catch { done(`a frame that is not JSON: ${String(d).slice(0, 40)}`); } });
33
+ ws.on('error', (e: Error) => { clearTimeout(timer); done(`the socket failed: ${e.message}`); });
34
+ });
35
+ return first === 'conversation_initiation_metadata' ? [] : [`the conversation's first event was ${first}`];
36
+ },
37
+ },
38
+ ];
@@ -0,0 +1,89 @@
1
+ // Gemini's official SDKs at the versions LibreChat pins: chat uses the Generative AI SDK,
2
+ // and its image tool uses Google Gen AI. Requests use the World's proxy through global fetch,
3
+ // the transport both unmodified SDKs use.
4
+ import type { ClientCase, ClientContext } from './harness.ts';
5
+
6
+ const REFERENCE = 'https://ai.google.dev/api/generate-content';
7
+ const MODEL = 'gemini-3.8-flash';
8
+ const PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9ZlSAAAAAASUVORK5CYII=';
9
+
10
+ async function routed<T>(c: ClientContext, run: () => Promise<T>): Promise<T> {
11
+ const previous = globalThis.fetch;
12
+ globalThis.fetch = c.fetch;
13
+ try { return await run(); } finally { globalThis.fetch = previous; }
14
+ }
15
+
16
+ async function chat(c: ClientContext, model = MODEL) {
17
+ const { GoogleGenerativeAI } = await import('@google/generative-ai');
18
+ return new GoogleGenerativeAI(c.env.GOOGLE_API_KEY ?? '').getGenerativeModel({ model }, { baseUrl: `https://${c.host}` });
19
+ }
20
+
21
+ export const cases: ClientCase[] = [
22
+ {
23
+ id: 'generative-ai-stream-aggregate',
24
+ asks: 'the chat SDK decodes SSE chunks and aggregates their text into the final response',
25
+ source: `${REFERENCE} "a stream of GenerateContentResponse instances"`,
26
+ async run(c) {
27
+ return routed(c, async () => {
28
+ c.scenario({ handlers: [{ id: 'words', on: { userTextIncludes: 'workshop' }, respond: { text: 'A scripted workshop reply.' } }] });
29
+ const result = await (await chat(c)).generateContentStream('Plan the workshop.');
30
+ let text = '';
31
+ for await (const chunk of result.stream) text += chunk.text();
32
+ const response = await result.response;
33
+ return text === 'A scripted workshop reply.' && response.text() === text && response.candidates?.[0]?.finishReason === 'STOP'
34
+ ? [] : ['SSE text, aggregate response or final finish reason did not survive the SDK'];
35
+ });
36
+ },
37
+ },
38
+ {
39
+ id: 'generative-ai-function-continuation',
40
+ asks: 'the SDK exposes function calls and sends their result as the next chat turn',
41
+ source: `${REFERENCE} "Function Calling"`,
42
+ async run(c) {
43
+ return routed(c, async () => {
44
+ const { SchemaType } = await import('@google/generative-ai');
45
+ c.scenario({ handlers: [
46
+ { id: 'returned', on: { toolResultFor: 'lookup_company', lastMessageIsToolResult: true }, respond: { text: 'Tidewater renewal is in October.' } },
47
+ { id: 'lookup', on: { hasTool: 'lookup_company' }, respond: { functionCalls: [{ name: 'lookup_company', args: { company: 'Tidewater' } }] } },
48
+ ] });
49
+ const session = (await chat(c)).startChat({ tools: [{ functionDeclarations: [{ name: 'lookup_company', parameters: { type: SchemaType.OBJECT, properties: { company: { type: SchemaType.STRING } }, required: ['company'] } }] }] });
50
+ const first = await session.sendMessage('Find the company renewal.');
51
+ const call = first.response.functionCalls()?.[0];
52
+ if (call?.name !== 'lookup_company' || (call.args as Record<string, unknown>).company !== 'Tidewater') return ['the SDK lost the function name or arguments'];
53
+ const next = await session.sendMessage([{ functionResponse: { name: call.name, response: { renewal: 'October' } } }]);
54
+ return next.response.text() === 'Tidewater renewal is in October.' ? [] : ['the SDK chat history/function response failed to continue the turn'];
55
+ });
56
+ },
57
+ },
58
+ {
59
+ id: 'generative-ai-typed-refusal',
60
+ asks: 'a missing model becomes the chat SDK HTTP error with its status and Google error details',
61
+ source: 'https://cloud.google.com/apis/design/errors "NOT_FOUND"',
62
+ async run(c) {
63
+ return routed(c, async () => {
64
+ const { GoogleGenerativeAIFetchError } = await import('@google/generative-ai');
65
+ try { await (await chat(c, 'missing-model')).generateContent('Hello'); }
66
+ catch (error) { return error instanceof GoogleGenerativeAIFetchError && error.status === 404 ? [] : ['missing model was not the SDK 404 error']; }
67
+ return ['missing model unexpectedly generated content'];
68
+ });
69
+ },
70
+ },
71
+ {
72
+ id: 'genai-image-parts',
73
+ asks: 'the image SDK reads generated text and inline image bytes from response parts',
74
+ source: `${REFERENCE} "inlineData"`,
75
+ async run(c) {
76
+ return routed(c, async () => {
77
+ const { GoogleGenAI } = await import('@google/genai');
78
+ c.scenario({ handlers: [{ id: 'picture', on: { userTextIncludes: 'square illustration' }, respond: {
79
+ text: 'A synthetic square illustration.', parts: [{ inlineData: { mimeType: 'image/png', data: PNG } }],
80
+ } }] });
81
+ const ai = new GoogleGenAI({ apiKey: c.env.GEMINI_API_KEY ?? '', httpOptions: { baseUrl: `https://${c.host}` } });
82
+ const response = await ai.models.generateContent({ model: 'gemini-3.1-flash-image', contents: 'Make a square illustration.', config: { responseModalities: ['TEXT', 'IMAGE'] } });
83
+ const image = response.candidates?.[0]?.content?.parts?.find((p) => p.inlineData)?.inlineData;
84
+ return response.text === 'A synthetic square illustration.' && image?.mimeType === 'image/png' && image.data === PNG
85
+ ? [] : ['the image SDK lost text, MIME type or fixture bytes'];
86
+ });
87
+ },
88
+ },
89
+ ];
@@ -0,0 +1,67 @@
1
+ // GitHub's own client (Octokit, @octokit/rest), driving the github pack as its applications do: twin-world's on-call
2
+ // recipe (cookbook/on-call-agent) reads a new issue by its number and answers on it with the ticket it filed, through
3
+ // Octokit with the World's GITHUB_TOKEN; and Octokit raises its typed error, with the response's status, when GitHub
4
+ // refuses (the recipe's issue read of a number the repository does not have). The repository the issue lives in is made
5
+ // as `gh repo create` makes one, by GraphQL's createRepository, through Octokit's own GraphQL call.
6
+ import type { ClientCase, ClientContext } from './harness.ts';
7
+
8
+ const SCRIPTING = 'https://docs.github.com/en/rest/guides/scripting-with-the-rest-api-and-javascript';
9
+ const ISSUES = 'https://docs.github.com/en/rest/issues/issues';
10
+ const COMMENTS = 'https://docs.github.com/en/rest/issues/comments';
11
+
12
+ async function octokit(c: ClientContext) {
13
+ const { Octokit } = await import('@octokit/rest');
14
+ return new Octokit({ auth: c.env.GITHUB_TOKEN ?? '', request: { fetch: c.fetch } });
15
+ }
16
+
17
+ /** A public repository of the token's own account, made as `gh repo create` makes one (GraphQL's createRepository). */
18
+ async function repository(client: Awaited<ReturnType<typeof octokit>>, name: string): Promise<{ owner: string; repo: string }> {
19
+ const made = await client.graphql<{ createRepository: { repository: { nameWithOwner: string } } }>(
20
+ 'mutation($name: String!) { createRepository(input: { name: $name, visibility: PUBLIC }) { repository { nameWithOwner } } }', { name });
21
+ const [owner, repo] = made.createRepository.repository.nameWithOwner.split('/') as [string, string];
22
+ return { owner, repo };
23
+ }
24
+
25
+ export const cases: ClientCase[] = [
26
+ {
27
+ id: 'on-call-triage',
28
+ asks: 'the on-call recipe reads a new issue by its number and answers on it, through Octokit: the issue as filed, the comment as written, and the issue counting it',
29
+ source: `${ISSUES} "Any user with pull access to a repository can create an issue."; ${COMMENTS} "You can use the REST API to list comments on issues and pull requests. Every pull request is an issue, but not every issue is a pull request."`,
30
+ async run(c) {
31
+ try {
32
+ const client = await octokit(c);
33
+ const { owner, repo } = await repository(client, 'storefront');
34
+ const filed = (await client.rest.issues.create({ owner, repo, title: 'Checkout fails on Safari', body: 'The pay button does nothing on Safari 18.' })).data;
35
+ const issue = (await client.rest.issues.get({ owner, repo, issue_number: filed.number })).data;
36
+ const out: string[] = [];
37
+ if (issue.number !== filed.number) out.push(`issues.get answered issue ${issue.number}, not ${filed.number}`);
38
+ if (issue.title !== 'Checkout fails on Safari') out.push(`the issue's title is ${JSON.stringify(issue.title)}`);
39
+ if (issue.state !== 'open') out.push(`the issue is ${issue.state}, not open`);
40
+ const body = 'Tracked as OPS-1. A bug: the checkout button on Safari.';
41
+ const comment = (await client.rest.issues.createComment({ owner, repo, issue_number: issue.number, body })).data;
42
+ if (comment.body !== body) out.push(`the comment's body is ${JSON.stringify(comment.body)}`);
43
+ if (comment.user?.login !== owner) out.push(`the comment is ${String(comment.user?.login)}'s, not the token's own ${owner}`);
44
+ const after = (await client.rest.issues.get({ owner, repo, issue_number: issue.number })).data;
45
+ if (after.comments !== 1) out.push(`the issue counts ${after.comments} comments, not the one made`);
46
+ return out;
47
+ } catch (e) { return [`the recipe's calls failed: ${(e as Error).name}: ${(e as Error).message}`]; }
48
+ },
49
+ },
50
+ {
51
+ id: 'missing-issue-raises',
52
+ asks: "reading an issue the repository does not have raises Octokit's HttpError with the response's status, 404",
53
+ source: `${SCRIPTING} "If an API error occurs even after retries, Octokit.js throws an error that includes the HTTP status code of the response ( response.status ) and the response headers ( response.headers )."`,
54
+ async run(c) {
55
+ try {
56
+ const client = await octokit(c);
57
+ const { owner, repo } = await repository(client, 'pager');
58
+ const raised = await client.rest.issues.get({ owner, repo, issue_number: 4242 }).then(() => undefined, (e: unknown) => e as { name?: string; status?: number });
59
+ if (!raised) return ['reading an issue the repository does not have answered one'];
60
+ const out: string[] = [];
61
+ if (raised.name !== 'HttpError') out.push(`it raised ${String(raised.name)}, not Octokit's HttpError`);
62
+ if (raised.status !== 404) out.push(`its status is ${String(raised.status)}, not 404`);
63
+ return out;
64
+ } catch (e) { return [`the case's calls failed: ${(e as Error).name}: ${(e as Error).message}`]; }
65
+ },
66
+ },
67
+ ];
@@ -0,0 +1,4 @@
1
+ // Groq's chat through openai-node (its OpenAI-compatible API), the shared chat cases.
2
+ import { chatCases } from './openai-compatible.ts';
3
+
4
+ export const cases = chatCases({ key: 'GROQ_API_KEY', base: (host) => `https://${host}/openai/v1`, model: 'test-model' });
@@ -0,0 +1,101 @@
1
+ // THE VENDOR'S OWN CLIENT — how conformance drives a pack the way an application does (docs/contributing/architecture.md,
2
+ // "Conformance"): the vendor's official SDK, unmodified, in a Node process as an application runs it, against the pack
3
+ // served as a World runs it. Its credentials are the ones the pack's own credential door issued; its connections reach the
4
+ // served port as a World's interception routes the vendor's hosts, the vendor's URL and Host unchanged (an HTTP request
5
+ // over node:http with its Host kept, a socket's TCP connection made to the served port). A case's expectations are the
6
+ // vendor's documented semantics, each cited, never the pack author's life.
7
+ import { createServer, request as httpRequest } from 'node:http';
8
+ import { Agent as HttpsAgent } from 'node:https';
9
+ import { connect, type AddressInfo, type Socket } from 'node:net';
10
+ import { writeFileSync } from 'node:fs';
11
+
12
+ /** What a case is given. */
13
+ export type ClientContext = {
14
+ /** the vendor's API host, as its client addresses it (the descriptor's first host) */
15
+ host: string;
16
+ /** the credentials the pack's credential door issued, by the environment variable a World sets each in */
17
+ env: Record<string, string>;
18
+ /** fetch as a World routes it: a URL on the vendor's host reaches the served pack, its Host kept */
19
+ fetch: typeof fetch;
20
+ /** a TCP connection to the served pack, for a socket client (the ws library's `createConnection`): the client keeps
21
+ * the vendor's URL and Host, and its TLS-less connection lands on the served port, as the World's interception does */
22
+ connect: () => Socket;
23
+ /** an HTTPS agent as a World routes it, for a client that takes an agent (Slack's, Stripe's): its connections reach the
24
+ * served port, the client's URL and Host unchanged */
25
+ agent: HttpsAgent;
26
+ /** writes the World's scenario file (read at the pack's next turn) */
27
+ scenario: (doc: unknown) => void;
28
+ };
29
+
30
+ /** One expectation of the vendor's documented behaviour, driven through its client. */
31
+ export type ClientCase = {
32
+ id: string;
33
+ /** what the vendor documents, as the case holds the pack to it */
34
+ asks: string;
35
+ /** the page that documents it: `<url> "<words>"` */
36
+ source: string;
37
+ /** what fails, each named; empty when the pack holds it */
38
+ run: (c: ClientContext) => Promise<string[]>;
39
+ };
40
+
41
+ /** The World's proxy in front of a pack served on `served`: it ends the caller's TLS (here, none was begun) and forwards
42
+ * each request and socket upgrade to the pack with `x-forwarded-proto: https`, the vendor's Host unchanged, as a World's
43
+ * proxy forwards a caller of the vendor's https host (twin-fetch.ts, twinPublicBase: the links and socket URLs the pack
44
+ * answers are then the vendor's secure ones). Answers the proxy's port. */
45
+ export async function worldProxy(served: number): Promise<number> {
46
+ const server = createServer((req, res) => {
47
+ const forward = httpRequest({ host: '127.0.0.1', port: served, method: req.method, path: req.url, headers: { ...req.headers, 'x-forwarded-proto': 'https' } }, (answer) => {
48
+ res.writeHead(answer.statusCode ?? 502, answer.headers);
49
+ answer.pipe(res);
50
+ });
51
+ forward.on('error', () => { res.writeHead(502); res.end(); });
52
+ req.pipe(forward);
53
+ });
54
+ server.on('upgrade', (req, socket, head) => {
55
+ const upstream = connect(served, '127.0.0.1', () => {
56
+ const lines = [`${req.method} ${req.url} HTTP/1.1`, ...Object.entries({ ...req.headers, 'x-forwarded-proto': 'https' }).flatMap(([k, v]) => (Array.isArray(v) ? v : [v]).map((x) => `${k}: ${x}`)), '', ''];
57
+ upstream.write(lines.join('\r\n'));
58
+ if (head.length) upstream.write(head);
59
+ upstream.pipe(socket);
60
+ socket.pipe(upstream);
61
+ });
62
+ upstream.on('error', () => socket.destroy());
63
+ socket.on('error', () => upstream.destroy());
64
+ });
65
+ await new Promise<void>((r) => server.listen(0, '127.0.0.1', () => r()));
66
+ server.unref();
67
+ return (server.address() as AddressInfo).port;
68
+ }
69
+
70
+ /** The context for a pack reached at `port` of this machine (the World's proxy in front of it). */
71
+ export function clientContext(port: number, host: string, env: Record<string, string>, scenarioPath: string): ClientContext {
72
+ const routedFetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
73
+ const request = new Request(input, init);
74
+ const url = new URL(request.url);
75
+ const body = request.method === 'GET' || request.method === 'HEAD' ? undefined : Buffer.from(await request.arrayBuffer());
76
+ const headers: Record<string, string> = Object.fromEntries(request.headers);
77
+ headers.host = url.host;
78
+ return new Promise<Response>((resolve, reject) => {
79
+ const sent = httpRequest({ host: '127.0.0.1', port, method: request.method, path: `${url.pathname}${url.search}`, headers }, (res) => {
80
+ const chunks: Buffer[] = [];
81
+ res.on('data', (c: Buffer) => chunks.push(c));
82
+ res.on('end', () => {
83
+ const answer = new Headers();
84
+ for (const [k, v] of Object.entries(res.headers)) if (v !== undefined) for (const one of Array.isArray(v) ? v : [v]) answer.append(k, one);
85
+ resolve(new Response(res.statusCode === 204 || res.statusCode === 304 ? null : Buffer.concat(chunks), { status: res.statusCode ?? 0, headers: answer }));
86
+ });
87
+ res.on('error', reject);
88
+ });
89
+ sent.on('error', reject);
90
+ sent.end(body);
91
+ });
92
+ }) as typeof fetch;
93
+ const agent = new (class extends HttpsAgent {
94
+ createConnection(): Socket { return connect(port, '127.0.0.1'); }
95
+ })({ keepAlive: false });
96
+ return {
97
+ host, env, fetch: routedFetch, agent,
98
+ connect: () => connect(port, '127.0.0.1'),
99
+ scenario: (doc) => writeFileSync(scenarioPath, JSON.stringify(doc)),
100
+ };
101
+ }
@@ -0,0 +1,9 @@
1
+ // Merge Gateway through the SDKs its callers use: openai-node on its chat completions, Anthropic's SDK on its Messages
2
+ // surface (both of its OpenAI- and Anthropic-compatible APIs), the shared cases.
3
+ import { messagesCases } from './anthropic-messages.ts';
4
+ import { chatCases } from './openai-compatible.ts';
5
+
6
+ export const cases = [
7
+ ...chatCases({ key: 'MERGE_GATEWAY_API_KEY', base: (host) => `https://${host}/v1`, model: 'zai/glm-5.3-flash' }),
8
+ ...messagesCases({ key: 'MERGE_GATEWAY_API_KEY', base: (host) => `https://${host}`, model: 'zai/glm-5.3-flash', toolTurn: { toolCalls: [{ name: 'lookup', arguments: {} }] } }),
9
+ ];