Commit Graph

5396 Commits

Author SHA1 Message Date
jkomyno 19f7f2a002 fix(cli): scope companion repair and keep unmeasured large output stored
- Import an in-process companion before checking the rest of the set, so a
  missing unrelated companion cannot fail generate or run through an offline
  repair. Repair runs only when the requested module fails to load.
- Check each companion's required exports, so a file left by another release
  fails with a typed reinstall error instead of calling a missing export.
- When the tokenizer cannot load, store any response past the byte pre-filter.
  The four-bytes-per-token estimate can undercount, so it no longer keeps a
  response inline.
- Bundle the executable graph check with the release build's env inlining,
  defines, NODE_ENV and syntax minification.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 17:46:44 +02:00
jkomyno af90b83c01 Merge branch 'next' into claude/cli-startup-bundle-diet-c7xicz
Resolve the generate and run loaders in favor of the generation companion, drop the entry modules it supersedes, and let the startup-imports test allow src/generation/errors.ts as the binary build guard does.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:28:04 +02:00
Daksh 968315b85e perf(cli): defer the TypeScript compiler and generation pipeline (#4468)
## Summary

`composio --version`: 622ms to 408ms. Eager module evaluation: 364ms to
130ms.

`commands/index.ts` builds the root command tree from every `.cmd.ts`,
so evaluating one command evaluated all of them. Two of them reached the
TypeScript compiler and the code generation pipeline at module scope.
`composio execute` paid ~165ms for a compiler it never called.

Stacked on #4464. Review #4463 and #4464 first.

Bun 1.4.1+4661e494f, linux-x64, best of 7, analytics disabled, same
script before and after:

| | before | after |
|---|---|---|
| `composio --version` | 622ms | 408ms |
| module evaluation | 363.8ms | 130.0ms |
| `commands/run.cmd` | 155.8ms | 8.0ms |
| `commands/generate` | 63.5ms | 2.5ms |

## Changes

`Command.withHandler` runs lazily, so moving an import inside a handler
body defers it. Specs, flags, descriptions and subcommand wiring still
resolve eagerly, so parsing, help and "did you mean" suggestions cannot
change.

1. `run.cmd.ts` was the only consumer of `import ts from 'typescript'`,
through three source rewrites `composio run` applies to a user script.
They move to `run-source-transforms.ts`, which the handler imports
dynamically. Tests import from the new path.
2. `ts.generate.cmd.ts` and `py.generate.cmd.ts` pulled
`src/generation/*` at module scope. Both resolve it inside the handler
now, right before first use.

These use `Effect.promise`, not `Effect.tryPromise`. A rejected import
of a module bundled into this binary is a broken build, not a
recoverable failure.

## Type of change
- [ ] Bug fix
- [ ] New feature
- [x] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change

## How Has This Been Tested?

Bun 1.4.1+4661e494f, Node 24.17.0, pnpm 11.8.0, linux-x64.

1. Built the binary before and after and diffed stdout, stderr and exit
code across 11 invocations: `--help` at root and for generate, generate
ts, generate py, run, tools and execute, plus `version`, `--version`, an
unknown command and an unknown flag. Identical. The error paths are
there on purpose; they exercise the parser and the suggestion code,
where a shifted tree would show first.
2. `pnpm run typecheck && pnpm run validate:boundaries && pnpm run
validate:skills`
3. `pnpm test`: 1326 passed, 1 skipped, 1 failed. The failure is
`test/src/cli-main.test.ts`, which spawns the CLI from source against a
15s timeout and takes ~24s in this container. It fails the same way on
the parent commit (25.6s and 25.2s there, 24.5s and 24.3s here).

Reproduce: `cd ts/packages/cli && pnpm build:binary && time
./dist/composio --version`.

After rebasing onto the updated #4463 and #4464: `pnpm run typecheck`
passes, and the `run`, `generate ts`, `generate py` and `execute` suites
pass (120 passed, 1 skipped). The code in this PR is unchanged.

## Screenshots (if applicable)

Not applicable.

## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [ ] I updated documentation as needed
- [ ] I added tests or explain why not applicable
- [ ] I added a changeset if this change affects published packages

No docs describe module loading order. No new tests; the existing suite
covers the moved functions, and the 11-invocation diff covers what this
could break. A test asserting the module is not loaded eagerly would be
good to have; #4469 adds a build-time check instead. `@composio/cli` is
private, so no changeset.

## Additional context

~130ms of eager evaluation remains. `services/agents` is 98ms of it:
Effect `Schema` definitions built at module scope. It cannot be deferred
as-is because `effects/handle-agent-auth-error.ts` narrows with `error
instanceof AgentAuthError` and six handlers depend on it. That is a
separate change.

The ~235ms pre-main bundle parse is unaffected. It scales with bundle
size, and a dynamic import keeps the module in the bundle. A binary that
bundles everything but runs only `console.log` still costs ~235ms. #4469
moves the code out of the bundle.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
@composio/cli@0.4.2-beta.390
2026-09-14 16:25:11 +02:00
jkomyno c9c3e49fee test(cli): wait for the hung host command before advancing the clock
The hung-native-host test forked setup, yielded once, then advanced the
TestClock by two minutes. Setup does real file I/O before it reaches the
host command, so on a slow CI worker the timeout was not yet armed when
the clock moved; the sleep then waited forever and the test failed on
vitest's own 15s limit. It flaked on next-based branches today, not
only this one. The hanging runner now opens a latch when setup reaches
it, and the test advances the clock only after that.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:19:30 +02:00
jkomyno c0053d7eeb test(cli): guard the startup path against eager compiler imports
A static import of typescript or src/generation anywhere on the command
tree silently restores ~165ms of module evaluation to every invocation,
and nothing failed. The test loads the command tree in a fresh Bun
process and asserts, via the module registry, that neither the compiler
nor the generation pipeline nor the run source transforms were
evaluated. Verified it fails on a stray static import.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:11:43 +02:00
jkomyno 6b0db59c30 refactor(cli): load each generation pipeline through one entry module
The generate handlers assembled their pipeline from three or four dynamic
imports and repackaged the results by hand. Each pipeline now has an
index module that re-exports what the handler needs, so the deferred
load is a single import and the boundary the handler crosses is named.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:11:43 +02:00
jkomyno 33e54a76a7 refactor(cli): import run source transforms through the src alias
Every other deferred import in the CLI spells its target with the src
alias; the run command was the one relative specifier.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:11:43 +02:00
jkomyno af573e5a15 refactor(cli): evict every memoized failure and drop the unused list error
memoizeInProcess evicted a key only on typed failures, but Effect.cached stores
the whole Exit, so a defect or an interrupted first caller was replayed for the
rest of the process. Evict on any failure cause.

ActiveConnectedAccountsListError was only ever unwrapped to its cause by both
callers, so the shared list now fails with the raw rejection.
2026-09-14 15:52:49 +02:00
jkomyno 4ce75c9245 fix(cli): keep large executes working when the encoder companion fails to load
A large execute measured its output after the tool call had already succeeded,
so a missing encoder companion whose repair download failed turned a successful
call into a failed command. Fall back to a byte-based token estimate instead.

Load companion modules with Effect.tryPromise so an unloadable file is a typed
RunCompanionRepairError rather than a defect, and add the two companions as
tsdown entries so the dist build can resolve them.

The executable graph check listed @composio/core's root entry with a pattern
that could never match Bun's relative module paths. The root entry is still
bundled behind the file-upload dynamic import, so drop that entry and correct
the constants comment.
2026-09-14 15:52:49 +02:00
DakshM on Exe (exe.dev) 023088a3a4 perf(cli): list connected accounts once per execute
`composio execute` listed the user's connected accounts twice on every
call: once toolkit-filtered by the account picker, once unfiltered by
session creation. The two code paths cannot see each other. The
unfiltered list is now fetched once per process and shared, and the
picker derives its toolkit subset from it: same slug match, server order
kept, first 100, exactly what its own query returned. If the shared list
is truncated (more active accounts than one page), the picker falls back
to its original request, so results are identical in every case.

Interleaved A/B against the parent commit, compiled binaries, 15 runs
each on a small response: 1735 -> 1636ms best, 1934 -> 1845ms median.
One fewer request on the critical path, ~140ms.

`memoizeInProcess` memoizes an Effect per key for the process lifetime,
shares one run between concurrent callers, and drops failures so the next
caller retries. It also covers `get_latest_version`, which the definition
refresh fetched twice with the same key on the stale path. The executor's
own version lookup stays unscoped and separate; scoping it to org and
project would change which definition it resolves, so that is left as is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx9gEjuiHux2weiHjdNcDs
2026-09-14 15:52:49 +02:00
DakshM on Exe (exe.dev) 0a1464d5e8 perf(cli): move the compiler and tokenizer out of the executable
`composio --version` goes from 288ms to 199ms, peak RSS from 97.8MB to
77.3MB, and the executable from 85.9MB to 79.7MB. Every command benefits.

A compiled Bun binary parses its whole embedded bundle before the first
line of JavaScript runs, and #4468 had already made sure the TypeScript
compiler and the tokenizer rank table were never *evaluated* unless
`generate`, `run`, or a large `execute` response needed them. They were
still *parsed* on every start: the compiler alone was 44% of the
executable's JavaScript and the o200k rank table another 28%, so
`--version` spent ~75ms reading code it could never call.

Both now ship as companion modules next to the executable, through the
mechanism `composio run` already uses for its own runtime helpers:

- `generation-runtime.mjs` carries `src/generation/*`, the `composio run`
  source rewrites, `typescript`, `@composio/ts-builders` and
  `openapi-typescript`. `generate ts`, `generate py` and `run` load it
  with `loadInstalledCompanionModule`; from a source checkout the loader
  resolves the `.ts` next to `run-companion-modules.ts` instead, so tests
  and `bun run src/bin.ts` need no build step.
- `execute-output-encoder-runtime.mjs` carries `js-tiktoken/lite` and the
  rank table. `execute` loads it only once a response exceeds the 10KB
  byte pre-filter.

A companion bundles its own copy of `effect`, and a fiber cannot run
primitives built by another copy of the runtime, so nothing Effect-shaped
crosses the boundary: the generation companion exposes plain functions
and promises, runs its pipelines on its own runtime, and returns failures
as values that `src/generation/errors.ts` rebuilds as the CLI's own error
classes, stack included. Generated output is byte-identical to #4468 for
`generate ts`, `generate ts --transpiled` and `generate py`.

Both modules join `RUN_COMPANION_MODULE_BASENAMES`, so the build, release
packaging, install verification, `upgrade` and the self-repair download
pick them up unchanged. The three hand-maintained uninstall lists and the
upgrade E2E fixture gain the two file names.

Two smaller startup costs go with it:

- `src/constants.ts` imported `constants` from `@composio/core`'s root
  entry for two strings and two URLs, which evaluated the whole SDK at
  startup (~25ms of module-scope work, mostly zod schemas). The four
  values are spelled out and pinned to core's by a test.
- `tool-file-uploads.ts` imported three core helpers at module scope that
  only a file upload reaches; they are imported on that path now.

The binary build gains a guard: after bundling the companions it bundles
`src/bin.ts` once more unminified and fails if the executable's graph
reaches `typescript`, `js-tiktoken`, core's root entry, `src/generation/*`
or a companion entry. Without it a stray static import would put the
compiler back into the executable with nothing to notice.

Building also surfaced that `assertBundledRuntimeFiles` blanked string
literals to same-length runs of spaces, which made the import patterns'
`^\s*` backtrack quadratically across the compiler's multi-megabyte
embedded lib strings and stalled the build for over ten minutes. String
bodies are dropped now. (The check itself has never matched a specifier,
since the specifiers it looks for are the string literals it removes;
that is left as it was.)

Measured on the pinned toolchain, Bun 1.4.1+4661e494f, linux-x64, best
of 15, telemetry disabled, both binaries built in the same session:

  composio --version       288ms -> 199ms
  tools execute --help     287ms -> 202ms
  peak RSS                 97.8MB -> 77.3MB
  executable               85.9MB -> 79.7MB
  executable JavaScript    8.3MB -> 2.1MB (minified)

The `execute` tail after `execute.tool_call.end` is unchanged for
responses under 10KB (~10ms) and ~20ms slower above it (351 -> 374ms),
which is the on-demand parse of the 2.2MB encoder companion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx9gEjuiHux2weiHjdNcDs
2026-09-14 15:52:49 +02:00
Claude 3b18bf38eb perf(cli): defer the TypeScript compiler and generation pipeline
`composio --version` drops from 622ms to 408ms, and eager module evaluation
from 363.8ms to 130.0ms, measured on the pinned toolchain (Bun
1.4.1+4661e494f, linux-x64, best of 7, analytics disabled).

`commands/index.ts` builds the root command tree from every `.cmd.ts` module,
so evaluating any one command evaluated all of them. Two of those modules
reached the TypeScript compiler and the code generation pipeline, which
nothing but `composio generate` and `composio run` ever calls:

   155.8ms -> 8.0ms   commands/run.cmd
    63.5ms -> 2.5ms   commands/generate

The command tree itself is untouched. `Command.withHandler(input => Effect)`
already runs lazily, so moving these imports inside the handler bodies is
enough. Specs, flags, descriptions, subcommand wiring and `root-help.ts`
introspection all still resolve eagerly, which is why parsing, help rendering
and "did you mean" suggestions cannot shift.

run.cmd.ts was the CLI's only consumer of `import ts from 'typescript'`,
through three source rewrites that `composio run` applies to a user script.
Those move to `run-source-transforms.ts`, which the handler imports
dynamically. The test suite imports them from the new path.

ts.generate.cmd.ts and py.generate.cmd.ts pulled `src/generation/*` at module
scope. Both now resolve it inside the handler, immediately before the first
use.

A rejected import of a module bundled into this binary is an impossible
invariant rather than a recoverable failure, so these use `Effect.promise`
rather than `Effect.tryPromise`. The module registry memoizes each import, so
repeat calls within one run cost nothing.

Behavior is unchanged, checked rather than assumed. Eleven invocations,
covering `--help` at root and for generate, generate ts, generate py, run,
tools and execute, plus `version`, `--version`, an unknown command and an
unknown flag, produce byte-identical stdout, stderr and exit codes before and
after.

Verified with typecheck (src and test), oxlint, validate:boundaries,
validate:skills, and the full package suite: 1326 passed, 1 skipped.
`test/src/cli-main.test.ts` times out in this container and does so identically
on the parent commit (25.6s and 25.2s there, 24.5s and 24.3s here) because it
spawns the CLI from source against a 15s timeout. Not caused by this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
2026-09-14 15:52:48 +02:00
Daksh 611a18726f fix(cli): stop tiktoken special-token literals from failing execute (#4464)
## Summary

`composio execute` exits 1 whenever a tool response contains the literal
text `<|endoftext|>` or `<|endofprompt|>`. A README about tokenizers is
enough. A 66-byte payload reproduces it.

The tool has already run by then. Side effects happen, stdout stays
empty, no session history entry is written. Someone who sent an email
this way sees a failure and has no response to inspect.

Cause: js-tiktoken's `encode(text, allowedSpecial = [],
disallowedSpecial = "all")`. The CLI passed only the text, so every
special token was disallowed and `encode` threw.

Stacked on #4463. GitHub retargets this to `next` once that merges.

## Changes

`countOutputTokens`, the only `encode` call, now passes `'all'`:

```ts
const countOutputTokens = (json: string): number =>
  getExecuteOutputEncoder().encode(json, 'all').length;
```

The encoder is a length gauge for the inline-vs-file decision, so
allowing the literals is the right reading. Each one counts as the
single special token it encodes to, not as the seven tokens its
characters would be. Counts for text without the literals do not change.

The byte pre-filter in #4463 hides this below 10KB by skipping the
encoder. That is cover, not a fix, which is why it stayed out of that
PR.

## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change

## How Has This Been Tested?

Bun 1.4.1+4661e494f, Node 24.17.0, pnpm 11.8.0, linux-x64.

1. New test drives `composio execute` through the `cli([...])` harness
with a response holding both literals, sized past the inline threshold
so the count is computed. It reads the stored file back and asserts both
literals survived.
2. Against the unfixed code it fails with `Error: The text contains a
special token that is not allowed: <|endoftext|>`. With the fix it
passes. Suite count goes 90 to 91.
3. `pnpm run typecheck && pnpm run validate:boundaries && pnpm exec
vitest run test/src/commands/tools/tools.execute.cmd.test.ts
test/src/commands/run.cmd.test.ts`

To see it by hand: point any tool at content holding one of the literals
and make the response larger than 10KB.

## Screenshots (if applicable)

Not applicable.

## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [ ] I updated documentation as needed
- [x] I added tests or explain why not applicable
- [ ] I added a changeset if this change affects published packages

No docs cover the threshold. `@composio/cli` is private, so no
changeset.

## Additional context

o200k defines exactly two special tokens. `encode` builds a regex from
the disallowed set and throws on the first match, before tokenizing.
That guard exists to stop callers from smuggling control tokens into a
model prompt. This code is measuring a string.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
@composio/cli@0.4.2-beta.389
2026-09-14 13:52:45 +00:00
jkomyno 9895726510 docs(cli): describe special-token literals as counted special tokens
encode(json, 'all') turns each literal into one special token rather than
counting its characters as text, so say that in the comment and the test name.
2026-09-14 15:51:31 +02:00
Claude 4f11a440e9 fix(cli): stop tiktoken special-token literals from failing execute
`Tiktoken.encode` signs as `encode(text, allowedSpecial = [],
disallowedSpecial = "all")`. The CLI passed only the text, so every special
token was disallowed and the call threw on any response containing the
literal `<|endoftext|>` or `<|endofprompt|>`. A 66-byte payload is enough.
Reading a README that documents a tokenizer hits it.

The throw landed in `prepareExecuteOutput`, after `spinner.stop('Execution
successful')` had already printed. So the tool had run, its side effects had
happened, and the CLI still exited 1 with nothing on stdout and no session
history entry.

Here the encoder is only a length gauge for the inline-versus-file decision,
so those literals are ordinary characters. Passing `allowedSpecial: 'all'`
counts them instead of rejecting the payload. Token counts are unchanged on
text that contains no special tokens.

The preceding commit's byte-length pre-filter hid this below 10KB by
skipping the encoder. Larger responses still reached it, and both call sites
were affected: the threshold check and the `tokenCount` reported for a
stored file. Both now go through one `countOutputTokens` helper.

The regression test drives the real command with a response holding both
literals, sized past the inline threshold so the count is actually computed.
Verified it fails without the fix, with the original error:

  Error: The text contains a special token that is not allowed: <|endoftext|>

Verified with typecheck (src and test), oxlint, validate:boundaries, and the
tools.execute and run command suites, now 90 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
2026-09-14 15:51:31 +02:00
Daksh 5468c242be perf(cli): cut 221ms and 44MB RSS off every CLI invocation (#4463)
## Summary

`composio --version`: 970ms to 749ms. Peak RSS: 175.8MB to 132.2MB.
Binary: 96MB to 86MB. No new dependencies (one unused one removed), no
API changes.

The compiled binary carried two copies of the TypeScript compiler and
all six tiktoken rank tables. A compiled Bun binary parses everything it
embeds before running any JS, so every command paid for that.

First PR in a stack of five. Review order: this, #4464, #4468, #4469,
#4475.

Bun 1.4.1+4661e494f, linux-x64, best of 7, analytics disabled:

| | before | after |
|---|---|---|
| `composio --version` | 970ms | 749ms |
| peak RSS | 175.8MB | 132.2MB |
| compiled binary | 96MB | 86MB |
| bundle | 31.24MB | 15.84MB |

## Changes

1. `run.cmd.ts` imported `ts` from ts-morph, which bundles its own
TypeScript. It now uses the `typescript` package the generation code
already pulls in. One compiler instead of two, 8.5MB each.
2. js-tiktoken's main entry inlines six rank tables (5.3MB). The CLI
only uses o200k. Switched to `js-tiktoken/lite` with that one table.
Token ids are identical.
3. `prepareExecuteOutput` built the rank table on every successful
execute just to compare against a 10,000 token threshold. A token covers
at least one byte, so a payload under 10,000 bytes cannot exceed 10,000
tokens. It checks bytes first and only builds the tokenizer past that.
It also checks the invocation origin before building it, since `composio
run` always prints inline, and a stored response is encoded once rather
than once for the threshold and again for `tokenCount`.
4. `ToolsExecutorLive` resolved a client via `clientSingleton.get()`
(disk reads) and then discarded it, since every caller passes one in.
Resolved lazily now.
5. `ts-morph` is removed from the CLI's dependencies. Nothing imports it
after change 1, and leaving it listed made it easy to bring its
TypeScript copy back.

What changes in behavior:

- Responses under 10KB no longer reach `Tiktoken.encode()`, so the
`<|endoftext|>` crash stops happening for them. Larger responses still
hit it. #4464 is the real fix.
- Executes started by `composio run` no longer build the tokenizer for
responses over 10KB. Their output was always printed inline, so the
count was discarded.
- TypeScript 6.0.2 (ts-morph's copy) becomes 6.0.3.
- Under `COMPOSIO_LOG_LEVEL=Debug`, ProjectContext's "resolved from"
lines no longer print on the remote execute path.

On change 3: an earlier version of this description said it saved ~500ms
per execute, measured in isolation under a different Bun. Inside the
compiled binary, `new Tiktoken(o200k)` costs ~390ms and `encode()` of
7.5KB about 4ms. Measured end to end on a real
`HACKERNEWS_GET_ITEM_WITH_ID` execute with `COMPOSIO_PERF_DEBUG=1`, the
time from `execute.tool_call.end` to exit drops from ~300ms to ~15ms for
responses under 10KB, and stays ~350ms above it.

## Type of change
- [ ] Bug fix
- [ ] New feature
- [x] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change

It removes a crash, but by accident, so it is not marked as a bug fix.

## How Has This Been Tested?

Bun 1.4.1+4661e494f, Node 24.17.0, pnpm 11.8.0, linux-x64.

1. `cd ts/packages/cli && pnpm run typecheck && pnpm run
validate:boundaries`
2. `pnpm exec vitest run
test/src/commands/tools/tools.execute.cmd.test.ts
test/src/commands/run.cmd.test.ts`: 90 passed. A new case covers a ~18KB
response that encodes to ~4k tokens, past the byte check but under the
threshold, and asserts it stays inline.
3. `pnpm build:binary && time ./dist/composio --version`

Checked but not committed: the three parse helpers give identical output
under both compilers across 20 sources (TSX, decorators, `using`,
`satisfies`, import attributes, unicode). Lite tiktoken gives identical
token id streams on 8 samples including CJK, RTL, emoji and control
characters. Max tokens per byte was 0.846, under the 1.0 the byte check
needs.

## Screenshots (if applicable)

Not applicable.

## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [ ] I updated documentation as needed
- [x] I added tests or explain why not applicable
- [ ] I added a changeset if this change affects published packages

No docs describe the bundle contents or the token threshold. The byte
pre-filter boundary has a test. The compiler and tokenizer comparisons
above are still not committed and should become a suite. `@composio/cli`
is `private: true`, so no changeset.

## Additional context

Bun 1.4.2 gives no gain over 1.4.1 (three rounds of best of 7:
723/732/756ms vs 744/713/747ms). Keep the pin.

Not touched: ~1.1s of execute preflight (5 to 7 serial round trips;
`project/resolve` has no cache and can fire three times), the
two-round-trip session create plus execute, and `--skip-checks`, which
currently skips nothing measurable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
@composio/cli@0.4.2-beta.388
2026-09-14 13:51:26 +00:00
jkomyno 1d3af803a3 test(cli): synchronize setup timeout clock 2026-09-14 15:51:05 +02:00
jkomyno cbcdecf3a9 perf(cli): skip the tokenizer for run-origin executes and count tokens once
Check the invocation origin before building the tokenizer, since composio run
always prints inline, and pass the token count from the threshold check into
the stored-output summary instead of encoding the payload twice.

Add a test for a response past the byte pre-filter but under the token
threshold, and drop the unused ts-morph dependency.
2026-09-14 15:46:57 +02:00
Claude ebe8bb780f perf(cli): cut 221ms and 44MB RSS off every CLI invocation
A compiled Bun binary parses its whole embedded module graph before the
first line of JS runs, so bundled-but-unused code is paid for on every
invocation. Verified: a binary that bundles everything but evaluates only
console.log still costs ~235ms, against 15ms for a hello-world build.
Most of this bundle was code `composio execute` never reaches.

Measured on the pinned toolchain (Bun 1.4.1+4661e494f, linux-x64,
best of 7, analytics disabled):

  composio --version   970ms   -> 749ms    (-221ms)
  peak RSS             175.8MB -> 132.2MB  (-43.6MB)
  compiled binary      96MB    -> 86MB
  bundle               31.24MB -> 15.84MB

Four changes.

run.cmd.ts imported `ts` from ts-morph, which vendors its own copy of the
TypeScript compiler, so the binary carried two of them. The file uses only
createSourceFile, forEachChild, ScriptTarget, ScriptKind and five isX
guards, all available in the typescript copy that
src/generation/typescript/* already pulls in. Sharing one compiler also
means commands/generate and commands/run.cmd no longer evaluate a compiler
each.

js-tiktoken's main entry statically inlines all six BPE rank tables (gpt2,
r50k, p50k, p50k_edit, cl100k, o200k) as string literals; the CLI only ever
uses o200k, via encodingForModel('gpt-4o'). The lite build with that single
table produces identical token-id streams.

prepareExecuteOutput built the o200k rank table on every successful
execute, purely to compare the response against a 10k-token threshold.
Constructing it measured ~390ms in a compiled binary on the pinned
toolchain (390.1, 367.1, 418.6ms across three runs), against ~4ms to
encode a 7.5KB payload once the table exists. A BPE token always covers at
least one UTF-8 byte, so a payload of at most THRESHOLD bytes can never
exceed THRESHOLD tokens; checking byte length first reaches the same
decision without the tokenizer.

That ~390ms is construction cost measured in isolation, not an end-to-end
delta on a real `composio execute`. No credentialed run was available to
measure the whole command before and after, so treat it as the size of the
work removed from the success path rather than a verified wall-clock
saving. `COMPOSIO_PERF_DEBUG=1` reports the gap between
`execute.tool_call.end` and exit for anyone able to run it for real.

ToolsExecutorLive resolved a client through clientSingleton.get()
unconditionally, walking the project context off disk, then discarded it
because every caller on the remote-execute path passes one in.

Three behavioral deltas, none of them the tokenization result or the
inline/file decision:

1. Tiktoken.encode() throws on the literals <|endoftext|> and
   <|endofprompt|> appearing anywhere in the response, at any size (a
   66-byte payload reproduces it). That throw landed after
   "Execution successful" had printed, so the tool ran and the CLI still
   exited 1 with nothing on stdout. Responses at or under 10KB no longer
   reach encode(), so they now succeed. Larger responses still hit it;
   the real fix is passing allowedSpecial 'all' and is not in this commit.
2. TypeScript 6.0.2 (ts-morph's vendored copy) to 6.0.3. Differential
   tested: the three real parse helpers over 20 sources covering TSX,
   decorators, `using`, `satisfies`, import attributes, optional-chained
   calls and unicode gave identical output on all 60 comparisons.
3. Under COMPOSIO_LOG_LEVEL=Debug, ProjectContext's "resolved from ..."
   debug lines no longer appear on the remote-execute path. The local-tool
   path still calls get() and is unchanged.

Verified with typecheck:src, oxlint, validate:boundaries, the
tools.execute and run command suites (89 tests), and differential tests of
both tokenizers and both TypeScript versions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
2026-09-14 15:46:57 +02:00
Alberto Schiabel 4bea066737 Apply suggestion from @jkomyno 2026-09-14 13:59:40 +02:00
Alberto Schiabel 117f48a331 Apply suggestion from @jkomyno 2026-09-14 13:58:37 +02:00
jkomyno 9a69683efb docs(core): fix tools getInput, proxyExecute, and execute examples
Correct the getInput JSDoc to pass the required text field, rewrite the
proxyExecute example with the real flat parameter shape and explain that
relative endpoints are appended to a base URL that may include a path, and
show where to find the version to pin for tools.execute.
2026-09-14 13:56:26 +02:00
Alberto Schiabel 33bbce2786 Merge branch 'next' into docs/changelog-update-cfeada6 2026-09-14 13:26:15 +02:00
Alberto Schiabel efe6d89864 chore(cli): refresh baked toolkit slugs (#4477)
## Summary
Automated refresh of the toolkit slugs the CLI knows without asking
the API, generated by
`ts/packages/cli/scripts/generate-toolkit-slugs.ts`.

Toolkits added since the last refresh currently cost users one
toolkit-list fetch (~2 s) the first time they run one of that
toolkit's tools. Merging this makes them free.

The generator refuses to write a list that is short, malformed, or
missing staple toolkits, so a bad fetch opens no PR at all.
@composio/cli@0.4.2-beta.387
2026-09-14 13:25:30 +02:00
Alberto Schiabel dae005cf41 fix(core): raise tool not found only on 404/400 (#4459)
This PR:

- closes [PRDE-1613](https://linear.app/composio/issue/PRDE-1613)
- maps only 404/400 from `tools.retrieve` to
`ComposioToolNotFoundError`; every other failure (401 invalid API key,
5xx, network) now raises the new `ComposioToolFetchError` with the
client error kept as `cause`
- `tools.get(userId, slug)` and `tools.execute` inherit the corrected
mapping since they call `getRawComposioToolBySlug`
- fixes `toolkits.get(slug)`, whose 404/400 check compared against the
OpenAI `APIError` class instead of the Composio client one, so
`ComposioToolkitNotFoundError` never fired
- Python parity: `get_raw_composio_tool_by_slug` raises
`ToolNotFoundError` (now a `NotFoundError` subclass) on 404/400 and
re-raises any other `composio_client` error unchanged
- adds unit tests on both sides for 404, 400, 401 and non-API failures;
verified live against the API with an invalid key on both SDKs

## Context

An unauthenticated call to `tools.getRawComposioToolBySlug` returned
`error.name === "ComposioToolNotFoundError"` while `error.cause.status`
was 401. The catch block wrapped every error except cancellation as
not-found, which predates the `@composio/client@beta` swap. Intended to
be back-ported to `main` after merging to `next`.

https://claude.ai/code/session_017HtbhwMAKcfebo8HyXWa5s
2026-09-14 13:25:08 +02:00
jkomyno 3ab81b3373 chore(cli): refresh baked toolkit slugs 2026-09-14 06:36:42 +00:00
Palash Kala 82341760c4 docs: describe the four verdict hints sessions filter on
Every tool carries at least one of readOnlyHint, createHint, updateHint
or destructiveHint, but the tag table and SDK reference docs only listed
the four MCP-spec hints, two of which (idempotentHint, openWorldHint)
are set on a minority of tools. Lead with the four verdict hints and
note the pinned-version gotcha on the v3 tools endpoints. The TypeScript
twoslash example stays on readOnlyHint until @composio/core ships the
widened enum (composio#4467).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHkYsmhteM1jJQoaoruiP3
2026-09-13 21:49:13 +05:30
CoralGarden52 dfd97b8a8e Update ts/packages/core/test/services/pusher.test.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-09-12 16:15:20 +08:00
jkomyno 8bb1d29950 fix(core): raise tool not found only on 404/400
getRawComposioToolBySlug relabelled every client error, including an
invalid API key (401), as ComposioToolNotFoundError. Map only 404/400 to
not-found and wrap the rest in a new ComposioToolFetchError that keeps
the client error as cause. Toolkits.getToolkitBySlug compared against
the OpenAI APIError class, so its not-found branch never fired; import
the Composio client class instead. Python mirrors the mapping: an
unknown slug raises ToolNotFoundError (now a NotFoundError), anything
else propagates the composio_client error unchanged.

PRDE-1613

Claude-Session: https://claude.ai/code/session_017HtbhwMAKcfebo8HyXWa5s
2026-09-11 22:54:53 +02:00
Brendan O'Leary 85e33f6a81 docs: update contact links with CTA placement (#4454)
## Summary
Route documentation contact links to the Composio contact form with both
`utm_source=docs` and page-specific CTA placement tracking.

## Changes
- Replace Calendly links on both rate-limit pages with
`https://composio.dev/contact?utm_source=docs&cta_placement=docs-rate-limits`.
- Update the data-retention sales link to use
`utm_source=docs&cta_placement=docs-data-retention`.
- Replace the premium-tools billing email link with the contact form
using `utm_source=docs&cta_placement=docs-pro-tools`.

## Type of change
- [x] Documentation

## How Has This Been Tested?
Verified that all four Markdown contact links contain the expected
source and page-specific placement parameters with Python URL parsing
assertions. `git diff --check` passed. The docs build and test suite
were not run; docs dependencies are not installed in this checkout.

## Checklist
- [x] I updated documentation as needed
- [x] I added tests or explain why not applicable

No new tests or changeset are needed for these four documentation URL
replacements.
2026-09-11 11:06:31 -07:00
Brendan O'Leary 11706780b3 docs: preserve source tracking on contact links 2026-09-11 17:58:12 +00:00
Brendan O'Leary 4b295a5813 docs: update contact links with CTA placement 2026-09-11 17:53:35 +00:00
Brendan O'Leary 08ec3f67de Remove fastmode as it isn't in the SDK yet (#4450)
## Summary
Explain the motivation and context for this change. Link to any related
issues.

Fixes #

## Changes
- 
- 

## Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change

## How Has This Been Tested?
Describe the tests you ran and instructions so reviewers can reproduce.
Include any relevant config/versions.

## Screenshots (if applicable)

## Checklist
- [ ] I have read the Code of Conduct and this PR adheres to it
- [ ] I ran linters/tests locally and they passed
- [ ] I updated documentation as needed
- [ ] I added tests or explain why not applicable
- [ ] I added a changeset if this change affects published packages

## Additional context
2026-09-11 09:58:04 -07:00
Brendan O'Leary 4c0c89b090 Remove fastmode as it isn't in the SDK yet 2026-09-11 09:52:33 -07:00
CoralGarden52 d5bc9253c9 test(core): model asynchronous Pusher dispatch 2026-09-11 15:38:22 +00:00
Alberto Schiabel 2367b80d9d chore(ci): enforce agent guidance validators in CI (#4447)
This PR:
- Add `.github/workflows/agent-substrate.yml` running `pnpm
validate:agent-skills` and `pnpm validate:skill-routing` on every push
and pull request; both validators previously ran in no CI workflow
- No path filters on the trigger: the stale-guidance walk scans every
text file in the repo, so any change can affect the result (PR runs
restore caches but only `next` pushes save them, per the
`setup-node-pnpm-bun` guidance)
- Skip `vendor/` directories in the `validate:agent-skills`
stale-guidance walk, which was failing on read-only third-party
snapshots mentioning other tools' rule conventions
- Extend the validator's command scan to `CONTRIBUTING.md` (with a `pnpm
dlx` exemption), so its documented commands are checked against
`package.json`, `python/Makefile`, and `python/noxfile.py` like the rest
of the guidance
- Point the routing-test header, root `AGENTS.md`, and
`skill-maintenance` reference docs at the new workflow, and add a
"Working with AI Coding Agents" section to `CONTRIBUTING.md` covering
the inherited agent setup, the two checks, and the routing-probe
requirement for skill edits

## Context

These two validators are the only checks keeping repo-level agent
guidance honest: command names mentioned in guidance are verified
against `package.json`, `python/Makefile`, and `python/noxfile.py`, and
routing probes assert each skill stays the unique top match for its
representative task. Until now nothing enforced either one, and the
stale-guidance walk was already red on vendored trees — a failure no
guidance owner could fix, which trains people to ignore the check. This
makes both checks blocking everywhere they can bite.

## Verification

- `pnpm validate:agent-skills` — 19 skills, green, now including
`CONTRIBUTING.md` commands
- `pnpm validate:skill-routing` — 19 probes over 19 skills, green
- Workflow YAML parsed; oxlint and prettier clean on touched files
- `Agent Substrate` workflow ran green on this PR (42s) before the
trigger change and re-runs on every push
2026-09-11 17:31:23 +02:00
CoralGarden52 efc2e5276d fix(core): contain async Pusher subscription errors 2026-09-11 15:29:52 +00:00
Brendan O'Leary 1a26ea0869 docs: document unattended agent authentication (#4434)
## Summary

Autonomous coding agents assumed Composio signup required a human,
leaving integrations without credentials or live verification. Add a
prominent guide for the supported `composio login --agent` flow when no
human is available.

Addresses Gauge action `cmtvu1rwe00040ip8mxhszmj1`. Reviewed the
metadata, insights, logs, and diffs for [evidence run
1](https://agents.withgauge.com/composio-aclx/runs/cmtvrts3n001201ea7jwbhu4q)
and [evidence run
2](https://agents.withgauge.com/composio-aclx/runs/cmtvrts3n001401eak0rwndar).
Both assumed signup required human interaction; the second attempted
disposable-email signup before switching to mocks.

## Changes

- Document unattended login, readiness checks, project API-key
extraction, credential handling, constraints, and the human login
fallback.
- Include a live Hacker News tool call and require separate verification
of the requested integration, including provider authorization and
confirmation of write results.
- Link the guide from the agent setup sidebar, quickstart, CLI docs, API
authentication reference, and `llms.txt`.

## Type of change

- [x] Documentation

## How Has This Been Tested?

From `docs/`:

- `bun run test`: 557 passed, 0 failed. Run with loopback access for the
analytics test's local server.
- `bun run lint:links`: 0 errors.
- `bun run lint`: passed with existing warnings.
- `bun run postinstall`: regenerated MDX collections successfully.

All five shell snippets pass `bash -n`. The key-extraction snippet
accepts a valid local fixture and rejects missing, blank, and non-string
keys without printing credentials. `git diff --check` passes. No live
account was provisioned or tool executed for this documentation change.

## Checklist

- [x] I ran linters/tests locally and they passed
- [x] I updated documentation as needed
- [x] I added tests or explain why not applicable: existing docs checks
and focused snippet validation cover this documentation-only change.
- [x] I added a changeset if this change affects published packages: not
applicable; no published package changes.
2026-09-11 08:25:59 -07:00
Brendan O'Leary 03f92ee026 docs: shorten unattended auth sidebar label 2026-09-11 15:20:02 +00:00
Brendan O'Leary 9968ea60ce Update docs/content/docs/security/token-custody.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-09-11 07:59:45 -07:00
Brendan O'Leary 0536957a38 docs: explain token custody architecture and deployment options 2026-09-11 14:44:44 +00:00
sdkrelease[bot] e265ff838a docs(kb): refresh public support knowledge (#4437)
Automated knowledge-base refresh for `ComposioHQ/support-knowledge`.

- Source commit: `5eac683455ff252a7a3b62f33ab6566445009b52` (unchanged;
rebuilt a stale semantic artifact)
- Regenerated public KB pages and search records
- Reused unchanged vectors and rebuilt the checked semantic artifact
- Ran KB freshness and semantic-artifact verification

Co-authored-by: sohambasu963 <80603154+sohambasu963@users.noreply.github.com>
2026-09-11 16:24:51 +02:00
Brendan O'Leary eb1d1e1d35 Merge branch 'next' into codex/docs-unattended-agent-auth 2026-09-10 16:33:20 -07:00
Brendan O'Leary 8174302053 docs: update documentation for new changelog entries (#4273)
## Summary
Automated documentation updates triggered by new changelog entries
merged to next.

Generated by Codex via GitHub Actions.
2026-09-10 16:32:46 -07:00
Brendan O'Leary abb272572b Merge branch 'next' into codex/docs-unattended-agent-auth 2026-09-10 16:22:29 -07:00
Brendan O'Leary ba84d94b02 Merge branch 'next' into docs/changelog-update-e2270d1 2026-09-10 16:22:21 -07:00
Brendan O'Leary 4634a99ae3 fix(docs): make semantic artifact freshness advisory in PR checks (#4436)
## Summary

Editing an indexed docs page makes the checked-in semantic artifact
stale, causing `Docs - Tests` to fail until a credentialed rebuild
commits new embeddings. Make freshness advisory in PR checks so docs
changes can proceed while the existing refresh workflows rebuild the
artifact.

## Changes

- Add `--allow-stale` to the PR semantic check. Stale artifacts emit a
warning; missing artifacts and integrity failures still fail the check.
- Validate artifact integrity before reporting freshness mismatches, so
stale content cannot hide corrupt vectors or invalid records.
- Keep rebuild workflows and runtime validation strict. Search uses its
existing keyword fallback until a fresh artifact is available.
- Document the contributor workflow and add regression coverage for
freshness, corruption, and CI wiring.

## Type of change

- [x] Bug fix
- [x] Documentation

## How Has This Been Tested?

Run from `docs/` with Bun 1.4.2:

- `bun test tests/static/kb-semantic-artifact.test.ts
tests/static/kb-update-workflow.test.ts
tests/static/knowledge-search.test.ts`: 72 passed.
- `bun run test`: 567 passed; one analytics test could not open its
local HTTP server in the sandbox. `bun test
tests/static/kb-query-analytics.test.ts` passed all 14 tests when rerun
with permission to listen.
- `bun run types:check`: passed.
- `bun run lint`: passed with existing warnings.
- `bun run check:kb-semantic` and `bun run check:kb-semantic
--allow-stale`: passed for the current artifact.
- CLI smoke checks with temporary artifact changes confirmed that stale
content exits 1 in strict mode and exits 0 with a GitHub warning in
advisory mode. A stale artifact with corrupt vector data still exits
nonzero. The original artifact was restored.

## Checklist

- [x] Ran linters and tests locally, with results recorded above
- [x] Updated documentation
- [x] Added regression tests
- [x] Changeset not required: docs-site and CI changes only

## Additional context

Merging stale embeddings temporarily reduces search to keyword retrieval
until a refresh lands. Embeddings are still generated by the existing
background workflows, without adding API calls to ordinary PR checks or
generating the corpus during search requests.
2026-09-10 16:22:08 -07:00
Brendan O'Leary 2ad6c8742f fix(docs): make semantic artifact freshness advisory in PR checks 2026-09-10 23:12:09 +00:00
Brendan O'Leary 1f44efb709 Merge branch 'next' into docs/changelog-update-e2270d1 2026-09-10 15:26:56 -07:00
Brendan O'Leary faaf37761c docs: update Python SDK reference from source (#4409)
## Summary
Auto-generated Python SDK reference docs from `python/composio/`.

Regenerates pages at `docs/content/reference/sdk-reference/python/` to
reflect changes in the Python package's public API (new methods, updated
signatures, changed types).
2026-09-10 15:23:13 -07:00