Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions .speakeasy/gen.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ management:
docVersion: 1.0.0
speakeasyVersion: 1.787.0
generationVersion: 2.914.0
releaseVersion: 1.2.5
configChecksum: 92f44d5cd212efdb767342ff76962bd6
releaseVersion: 1.2.6
configChecksum: 5bbe4aa9e30b0165d6e6567fea50b5d9
repoURL: https://github.com/OpenRouterTeam/typescript-sdk.git
installationURL: https://github.com/OpenRouterTeam/typescript-sdk
published: true
persistentEdits:
generation_id: f55ce102-f99e-4b95-95fe-fc5a307169c6
pristine_commit_hash: 5cada18aa7c5670817e1eaf2108927122b4492a8
pristine_tree_hash: e52e77fb09a094942f8e01dd300ea7d5129e8845
generation_id: 68bae83e-9b68-4ce0-8ad6-a6c6068ccca0
pristine_commit_hash: 8dcd85ae1a6350ccf72b5dd7ec3b2cac77a41fda
pristine_tree_hash: 837dd36abd8e397b97ac852545e0e1748dfdfd45
features:
typescript:
acceptHeaders: 2.81.2
Expand Down Expand Up @@ -12741,12 +12741,12 @@ trackedFiles:
pristine_git_object: 410efafd6a7f50d91ccb87131fedbe0c3d47e15a
jsr.json:
id: 7f6ab7767282
last_write_checksum: sha1:4487b371ab1978bd91d9e6c7321afcd2f549bda4
pristine_git_object: 94a3242648365f303b2bf03cfe6f79e246465536
last_write_checksum: sha1:e363c1cc9b383dfc3a887bb91b8f100d8c5a13e0
pristine_git_object: bbd839f7fa3076463efedccc5456daf7bfba2420
package.json:
id: 7030d0b2f71b
last_write_checksum: sha1:df3f8fe2c207300a30a0a9344d9f2d288178e78c
pristine_git_object: f7255ec02372915d4db5057979796cd4ce6729b1
last_write_checksum: sha1:f93265930913ef79f0badce5a5962821e98c7aab
pristine_git_object: e442edfb393489b5692228a32bf8cb9d4fcc1dd6
src/core.ts:
id: f431fdbcd144
last_write_checksum: sha1:5aa66b0b6a5964f3eea7f3098c2eb3c0ee9c0131
Expand Down Expand Up @@ -13145,20 +13145,20 @@ trackedFiles:
pristine_git_object: bb0c15148be25feb935e2d50c35c072b516cbcb5
src/lib/base64.ts:
id: "598522066688"
last_write_checksum: sha1:26b234d589cc15afab76ac7aaba1dd1bd4b4a84c
last_write_checksum: sha1:5e8eb1f050e47f489cc4698107bbfe3e26c43f3d
pristine_git_object: a187e58707bdb726ca2aff74941efe7493422d4e
src/lib/config.ts:
id: 320761608fb3
last_write_checksum: sha1:17c88f470a6039ad7159eb5ef120fe6295829b8f
pristine_git_object: e3446dbd670ebb474e18d2aee29dc75df62fa034
last_write_checksum: sha1:db03c1bac38d342aa0e1d3d3e4ee2391543489c3
pristine_git_object: 6b5918f2c141a9872c49b357751cfc44cd96dd89
src/lib/dlv.ts:
id: b1988214835a
last_write_checksum: sha1:eaac763b22717206a6199104e0403ed17a4e2711
pristine_git_object: f4c75aca30d4e0610454c7836af262a5a0cf7623
deleted: true
src/lib/encodings.ts:
id: 3bd8ead98afd
last_write_checksum: sha1:a74725064d06b6994b95873c037975d2f2e63467
last_write_checksum: sha1:80c84d1404e35b723e20648398f3f13e3e1d72dc
pristine_git_object: 49f15904923362434dfcdd02476c0487210d2f1a
src/lib/env.ts:
id: c52972a3b198
Expand Down Expand Up @@ -13203,7 +13203,7 @@ trackedFiles:
pristine_git_object: 35b0fb3e60638aa3d9ed11c19da774255cb05052
src/lib/sdks.ts:
id: 8a6d91f1218d
last_write_checksum: sha1:2ad4fe931d24de5dd737424cc82cd8ddab1c9d66
last_write_checksum: sha1:c847a6f3659887742f372368f568d873af59cfc4
pristine_git_object: fed3c8256d26f6a8f119255cd231d4af2c16cf77
src/lib/security.ts:
id: 0502afa7922e
Expand Down Expand Up @@ -17936,4 +17936,3 @@ examples:
"500":
application/json: {"error": {"code": 500, "message": "Internal Server Error"}}
examplesVersion: 1.0.2
releaseNotes: "## Typescript SDK Changes:\n* `openrouter.analytics.getUserActivity()`: \n * `request` **Changed**\n * `response.data[].workspaceId` **Added**\n* `openrouter.generations.getGeneration()`: `response.data.workspaceId` **Added**\n"
2 changes: 1 addition & 1 deletion .speakeasy/gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ generation:
documentation: mintlify
preApplyUnionDiscriminators: true
typescript:
version: 1.2.5
version: 1.2.6
acceptHeaderEnum: false
additionalDependencies:
dependencies:
Expand Down
2 changes: 1 addition & 1 deletion .speakeasy/workflow.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ targets:
sourceRevisionDigest: sha256:673a878a4627855ecde98dd43bcfd5d5fe70dba8fba956cfed66be2d033368d6
sourceBlobDigest: sha256:ad52a5d986bf19682306eff8bb475c11074dc16593c4e4937117d9d72e07f297
codeSamplesNamespace: open-router-chat-completions-api-typescript-code-samples
codeSamplesRevisionDigest: sha256:03ded13c05d37b77c8555988abc536a03fbc521a4ea9d9bc1a983f595d14b19f
codeSamplesRevisionDigest: sha256:75bf5a2ea7bbb8f1b7ef892dff80f423feea021e70e825d3713abccb50822e40
workflow:
workflowVersion: 1.0.0
speakeasyVersion: 1.787.0
Expand Down
29 changes: 29 additions & 0 deletions benchmarks/characterize/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SDK bundle and memory characterization

This harness measures four isolated SDK paths without network access:

- importing and constructing the root SDK;
- creating authenticated transport requests;
- validating a representative chat request;
- parsing 1,000 deterministically fragmented SSE events.

Run it from the repository root:

```sh
pnpm benchmark:characterize --runs=5
pnpm benchmark:characterize --runs=7 --json
```

Each case is bundled independently with esbuild. The harness explicitly enables bundling,
minification, and tree shaking and reports the resulting raw, gzip level 9, and Brotli quality 11
byte counts. Dependencies are included in each bundle.

Memory measurements run each bundle in multiple fresh `node --expose-gc` processes and report the
median. `import heap` and `import RSS` are retained deltas after forced garbage collection.
`scenario peak` is the largest sampled heap increase while the deterministic workload runs, and
`scenario retained` is the post-workload heap delta after forced garbage collection. The absolute
post-import heap and RSS values are also present in the JSON report.

Use the same machine, Node version, run count, and source revision for before/after comparisons.
Memory figures can vary across operating systems and Node/V8 releases; bundle byte counts are the
more stable cross-machine signal.
85 changes: 85 additions & 0 deletions benchmarks/characterize/RESULTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# ECO-2747 SDK characterization

Measured on macOS arm64 with Node 24.18.0, esbuild 0.25.11, and five fresh processes per
case. For a controlled comparison, the baseline restored the three optimized generated
files from `cea17fa4` while holding the harness and package metadata constant. Both
measurements used:

```sh
node benchmarks/characterize/index.mjs --runs=5 --json
```

Every bundle used ESM output, bundled dependencies, minification, and explicit
`treeShaking: true`, targeting Node 22.

## Before and after

- Root import bundle: raw 661,969 → 661,787 bytes; gzip 125,630 → 125,589; Brotli
97,006 → 96,995. Median retained import heap delta was 118,906,904 → 118,955,320
bytes and RSS delta was 229,212,160 → 230,113,280 bytes.
- Transport bundle: raw 118,279 → 118,098 bytes; gzip 32,898 → 32,878; Brotli
28,565 → 28,524. Median retained import heap delta was 973,592 → 965,016 bytes and
RSS delta was 4,505,600 → 4,292,608 bytes.
- Validation bundle: raw 135,048 → 135,048 bytes; gzip 36,803 → 36,803; Brotli
32,110 → 32,110. Median retained import heap delta was 13,414,304 → 13,410,560
bytes and RSS delta was 30,736,384 → 29,835,264 bytes.
- SSE fragmentation bundle: raw 2,750 → 2,750 bytes; gzip 1,386 → 1,386; Brotli
1,251 → 1,251. Median retained import heap delta was 373,760 → 373,760 bytes and
RSS delta was 2,244,608 → 2,310,144 bytes.

The deterministic outputs were identical before and after: root methods remained
functions; transport produced the same URL, headers, authorization, cookie, and body;
validation produced the same outbound JSON shape; and SSE parsing produced 1,000 events
and 23,890 characters. Small memory differences outside transport are measurement noise.

## Generator constraint

The pinned Speakeasy CLI 1.787.0 accepts `useIndexModules: false`, but this specification
cannot currently regenerate with it. The generated direct imports collide with local
operation wrapper names, for example `ListScimGroupsResponse`, and the generator's compile
step fails with `TS2440`, `TS2395`, `TS2448`, and `TS2454`. Keeping that output would
require generated-code alias patches or public model renames, neither of which is a safe
or generator-owned SDK optimization.

The retained optimization splits codec-only base64 helpers from the Zod adapters and
redirects transport imports to the codec module. Speakeasy persistent edits preserved
all three generated-file import changes during a successful pinned regeneration.

## Twenty-run Responses attribution

The expanded matrix ran each minified, tree-shaken bundle in 20 fresh processes.
Median retained import heap deltas were:

- `OpenRouterCore`: 965,552 bytes.
- `responsesSend`: 41,988,580 bytes.
- `ResponsesRequest$outboundSchema`: 21,757,296 bytes.
- `StreamEvents$inboundSchema`: 21,580,420 bytes.
- `TextDeltaEvent$inboundSchema`: 1,087,232 bytes.
- `StreamEventsResponseCompleted$inboundSchema`: 18,010,468 bytes.

This isolates the impactful remaining target: the transport and SSE framing runtime
are small, while the generated Responses sender eagerly constructs both broad request
and stream-event schema graphs. A text-delta-specific schema uses about 95% less retained
import heap than the all-event union, but the completion schema still loads the full
response/output graph.

Lazy event dispatch alone would improve startup but not invocation peak because the
completion event eventually loads its 18 MB graph. A material peak reduction requires
generator-owned operation-private validators that dispatch request tools, stream events,
and completed output items by discriminator without constructing every unused branch.
That remains a generator architecture change; it must pass the exact schema/error
equivalence suite before replacing the public generated schemas.

## Rejected: dynamic status-error imports

A persistent-edit prototype replaced the fourteen eager Responses HTTP error
schemas with status-specific dynamic imports. Across 20 fresh processes it
regressed the `responsesSend` import from 41,988,580 to 99,969,304 median heap
bytes and increased the minified bundle from 213,200 to 357,667 bytes.

With the current non-splitting Worker/Node bundle, esbuild retained the dynamic
module graph and its initialization wrappers instead of providing an isolated
status chunk. The prototype was fully reverted. Status-lazy schemas are only
viable if the deployment produces real code-split modules and measures their
combined Worker startup/first-error behavior; they are not a safe optimization
for the current bundle.
7 changes: 7 additions & 0 deletions benchmarks/characterize/entries/completed-event-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { StreamEventsResponseCompleted$inboundSchema } from '../../../src/models/streameventsresponsecompleted.ts';

export function run() {
return {
schema: StreamEventsResponseCompleted$inboundSchema.constructor.name,
};
}
11 changes: 11 additions & 0 deletions benchmarks/characterize/entries/core-import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { OpenRouterCore } from '../../../src/core.ts';

export function run() {
const client = new OpenRouterCore({
apiKey: 'benchmark-key',
serverURL: 'https://benchmark.invalid/api/v1',
});
return {
baseURL: client._baseURL?.toString(),
};
}
19 changes: 19 additions & 0 deletions benchmarks/characterize/entries/responses-request-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ResponsesRequest$outboundSchema } from '../../../src/models/responsesrequest.ts';

const request = {
model: 'openai/gpt-5.6-luna',
input: 'Return a deterministic response.',
maxOutputTokens: 64,
stream: true,
};

export function run({ sample }: { sample: () => void }) {
let output: ReturnType<typeof ResponsesRequest$outboundSchema.parse> | undefined;
for (let iteration = 0; iteration < 1_000; iteration++) {
output = ResponsesRequest$outboundSchema.parse(request);
if (iteration % 50 === 0) {
sample();
}
}
return output;
}
7 changes: 7 additions & 0 deletions benchmarks/characterize/entries/responses-send-import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { responsesSend } from '../../../src/funcs/responsesSend.ts';

export function run() {
return {
responsesSend: typeof responsesSend,
};
}
14 changes: 14 additions & 0 deletions benchmarks/characterize/entries/root-import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { OpenRouter } from '../../../src/index.ts';

export function run({ sample }: { sample: () => void }) {
const sdk = new OpenRouter({
apiKey: 'benchmark-key',
serverURL: 'https://benchmark.invalid/api/v1',
});
sample();

return {
callModel: typeof sdk.callModel,
chatSend: typeof sdk.chat.send,
};
}
69 changes: 69 additions & 0 deletions benchmarks/characterize/entries/sse-fragmentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { EventStream } from '../../../src/lib/event-streams.ts';

const eventCount = 1_000;
const widths = [
1,
2,
3,
5,
8,
13,
];

function fragmentedSource(bytes: Uint8Array): ReadableStream<Uint8Array> {
let offset = 0;
let chunkIndex = 0;
return new ReadableStream<Uint8Array>(
{
pull(controller) {
if (offset === bytes.length) {
controller.close();
return;
}
const end = Math.min(bytes.length, offset + widths[chunkIndex % widths.length]);
controller.enqueue(bytes.slice(offset, end));
offset = end;
chunkIndex++;
},
},
{
highWaterMark: 0,
},
);
}

export async function run({ sample }: { sample: () => void }) {
const payload = Array.from(
{
length: eventCount,
},
(_, index) => `data: event-${index} 👋\r\ndata: second line\r\n\r\n`,
).join('');
const source = fragmentedSource(new TextEncoder().encode(payload));
const stream = new EventStream(source, (message) => ({
done: false,
value: message.data ?? '',
}));
let count = 0;
let characterCount = 0;

for await (const event of stream) {
const expected = `event-${count} 👋\nsecond line`;
if (event !== expected) {
throw new Error(`event ${count} changed: ${JSON.stringify(event)}`);
}
characterCount += event.length;
count++;
if (count % 25 === 0) {
sample();
}
}

if (count !== eventCount) {
throw new Error(`expected ${eventCount} events, received ${count}`);
}
return {
characterCount,
eventCount: count,
};
}
22 changes: 22 additions & 0 deletions benchmarks/characterize/entries/stream-events-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { StreamEvents$inboundSchema } from '../../../src/models/streamevents.ts';

const event = {
type: 'response.output_text.delta',
sequence_number: 1,
item_id: 'message_1',
output_index: 0,
content_index: 0,
delta: 'deterministic delta',
logprobs: [],
};

export function run({ sample }: { sample: () => void }) {
let output: ReturnType<typeof StreamEvents$inboundSchema.parse> | undefined;
for (let iteration = 0; iteration < 10_000; iteration++) {
output = StreamEvents$inboundSchema.parse(event);
if (iteration % 100 === 0) {
sample();
}
}
return output?.type;
}
15 changes: 15 additions & 0 deletions benchmarks/characterize/entries/text-delta-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { TextDeltaEvent$inboundSchema } from '../../../src/models/textdeltaevent.ts';

const event = {
type: 'response.output_text.delta',
sequence_number: 1,
item_id: 'message_1',
output_index: 0,
content_index: 0,
delta: 'deterministic delta',
logprobs: [],
};

export function run() {
return TextDeltaEvent$inboundSchema.parse(event).type;
}
Loading
Loading