tjs-lang 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +33 -13
- package/README.md +4 -4
- package/bin/dev.ts +5 -1
- package/demo/docs.json +14 -2
- package/demo/index.html +2 -2
- package/demo/src/capabilities.ts +109 -2
- package/demo/src/demo-nav.ts +137 -203
- package/demo/src/imports.ts +43 -9
- package/demo/src/index.ts +179 -29
- package/demo/src/playground-shared.ts +11 -4
- package/demo/src/playground.ts +2 -2
- package/demo/src/tjs-playground.ts +294 -11
- package/demo/src/ts-playground.ts +239 -0
- package/dist/index.js +135 -127
- package/dist/index.js.map +6 -5
- package/dist/src/cli/commands/emit.d.ts +3 -0
- package/dist/src/lang/emitters/dts.d.ts +48 -0
- package/dist/src/lang/emitters/from-ts.d.ts +2 -0
- package/dist/src/lang/index.d.ts +1 -0
- package/dist/tjs-batteries.js +3 -3
- package/dist/tjs-batteries.js.map +2 -2
- package/dist/tjs-full.js +135 -127
- package/dist/tjs-full.js.map +6 -5
- package/dist/tjs-transpiler.js +2 -349
- package/dist/tjs-transpiler.js.map +4 -19
- package/package.json +1 -1
- package/src/cli/commands/emit.ts +26 -0
- package/src/cli/tjs.ts +4 -1
- package/src/lang/codegen.test.ts +55 -0
- package/src/lang/emitters/dts.test.ts +406 -0
- package/src/lang/emitters/dts.ts +588 -0
- package/src/lang/emitters/from-ts.ts +244 -20
- package/src/lang/index.ts +5 -0
- package/src/lang/typescript-syntax.test.ts +358 -0
package/CLAUDE.md
CHANGED
|
@@ -4,14 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
|
-
**tjs-lang** (npm: `tjs-lang`) is a
|
|
7
|
+
**tjs-lang** (npm: `tjs-lang`) is a typed JavaScript platform — a language, runtime, and toolchain that transpiles TypeScript and TJS to JavaScript with runtime type validation, inline WASM, monadic errors, and safe eval. It also includes AJS, a gas-metered VM for executing untrusted agent code in any JavaScript environment.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Three pillars:**
|
|
10
10
|
|
|
11
|
-
**
|
|
12
|
-
|
|
13
|
-
- **
|
|
14
|
-
- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution
|
|
11
|
+
- **TJS** — TypeScript-like syntax where types are examples that survive to runtime as contracts, documentation, and tests. Transpiles TS → TJS → JS in a single fast pass.
|
|
12
|
+
- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution with fuel limits and injected capabilities. Code travels to data.
|
|
13
|
+
- **Toolchain** — Compresses transpilation, linting, testing, and documentation generation into one pass. Includes inline WASM with SIMD, polymorphic dispatch, local class extensions, and a browser-based playground.
|
|
15
14
|
|
|
16
15
|
## Common Commands
|
|
17
16
|
|
|
@@ -70,7 +69,7 @@ npm run functions:serve # Local functions emulator
|
|
|
70
69
|
### Key Source Files
|
|
71
70
|
|
|
72
71
|
- `src/index.ts` - Main entry, re-exports everything
|
|
73
|
-
- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~
|
|
72
|
+
- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~3000 lines, security-critical)
|
|
74
73
|
- `src/vm/vm.ts` - AgentVM class (~226 lines)
|
|
75
74
|
- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)
|
|
76
75
|
- `src/builder.ts` - TypedBuilder fluent API (~19KB)
|
|
@@ -153,8 +152,8 @@ function greet(name: '') -> '' {
|
|
|
153
152
|
}
|
|
154
153
|
`
|
|
155
154
|
|
|
156
|
-
const
|
|
157
|
-
//
|
|
155
|
+
const jsResult = tjs(tjsSource)
|
|
156
|
+
// jsResult.code contains JavaScript with __tjs metadata for runtime validation
|
|
158
157
|
```
|
|
159
158
|
|
|
160
159
|
**Full Chain Example:**
|
|
@@ -223,10 +222,12 @@ AJS expressions behave differently from JavaScript in several important ways:
|
|
|
223
222
|
- Unit tests alongside source files (`*.test.ts`)
|
|
224
223
|
- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)
|
|
225
224
|
- Security tests in `src/use-cases/malicious-actor.test.ts`
|
|
226
|
-
- Language tests in `src/lang
|
|
225
|
+
- Language tests split across 14 files in `src/lang/` (lang.test.ts, features.test.ts, codegen.test.ts, parser.test.ts, from-ts.test.ts, wasm.test.ts, etc.)
|
|
227
226
|
|
|
228
227
|
Coverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.
|
|
229
228
|
|
|
229
|
+
**Bug fix rule:** Always create a reproduction test case before fixing a bug.
|
|
230
|
+
|
|
230
231
|
## Key Patterns
|
|
231
232
|
|
|
232
233
|
### Adding a New Atom
|
|
@@ -692,9 +693,28 @@ The `docs/` directory contains real documentation (markdown), not build artifact
|
|
|
692
693
|
- `DOCS-TJS.md` — TJS language guide
|
|
693
694
|
- `DOCS-AJS.md` — AJS runtime guide
|
|
694
695
|
- `CONTEXT.md` — Architecture deep dive
|
|
695
|
-
- `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done)
|
|
696
|
+
- `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done). **Critical**: work is NOT complete until `git push` succeeds; use `bd ready` to find work, `bd close <id>` to complete
|
|
696
697
|
- `PLAN.md` — Roadmap
|
|
697
698
|
|
|
698
|
-
### Known Gotcha:
|
|
699
|
+
### Known Gotcha: `tjs()` Returns an Object, Not a String
|
|
700
|
+
|
|
701
|
+
`tjs(source)` returns `{ code, types, metadata, testResults, ... }`. Use `.code` to get the transpiled JavaScript string. This is a common mistake.
|
|
702
|
+
|
|
703
|
+
### Known Gotcha: Running Emitted TJS Code in Tests
|
|
704
|
+
|
|
705
|
+
Transpiled TJS code requires `globalThis.__tjs` to be set up with `createRuntime()` before execution:
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
import { createRuntime } from '../lang/runtime'
|
|
709
|
+
|
|
710
|
+
const saved = globalThis.__tjs
|
|
711
|
+
globalThis.__tjs = createRuntime()
|
|
712
|
+
try {
|
|
713
|
+
const fn = new Function(result.code + '\nreturn fnName')()
|
|
714
|
+
// ... test fn
|
|
715
|
+
} finally {
|
|
716
|
+
globalThis.__tjs = saved
|
|
717
|
+
}
|
|
718
|
+
```
|
|
699
719
|
|
|
700
|
-
|
|
720
|
+
A `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.
|
package/README.md
CHANGED
|
@@ -272,10 +272,10 @@ The cost of "safe eval"—compare to a 200MB Docker image:
|
|
|
272
272
|
|
|
273
273
|
| Bundle | Size | Gzipped |
|
|
274
274
|
| ------------------------- | ------ | --------- |
|
|
275
|
-
| VM only |
|
|
276
|
-
| + Batteries (LLM, vector) |
|
|
277
|
-
| + Transpiler |
|
|
278
|
-
| Full (with TS support) |
|
|
275
|
+
| VM only | 218 KB | **66 KB** |
|
|
276
|
+
| + Batteries (LLM, vector) | 14 KB | 5 KB |
|
|
277
|
+
| + Transpiler | 5 KB | 2 KB |
|
|
278
|
+
| Full (with TS support) | 230 KB | 71 KB |
|
|
279
279
|
|
|
280
280
|
**Dependencies:** `acorn` (JS parser), `tosijs-schema` (validation). Both have zero transitive dependencies.
|
|
281
281
|
|
package/bin/dev.ts
CHANGED
|
@@ -181,6 +181,7 @@ const server = Bun.serve({
|
|
|
181
181
|
headers: {
|
|
182
182
|
'Content-Type': contentTypes[ext || 'html'] || 'text/plain',
|
|
183
183
|
'Access-Control-Allow-Origin': '*',
|
|
184
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
184
185
|
},
|
|
185
186
|
})
|
|
186
187
|
}
|
|
@@ -189,7 +190,10 @@ const server = Bun.serve({
|
|
|
189
190
|
const indexFile = Bun.file(join(DOCS_DIR, 'index.html'))
|
|
190
191
|
if (await indexFile.exists()) {
|
|
191
192
|
return new Response(indexFile, {
|
|
192
|
-
headers: {
|
|
193
|
+
headers: {
|
|
194
|
+
'Content-Type': 'text/html',
|
|
195
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
196
|
+
},
|
|
193
197
|
})
|
|
194
198
|
}
|
|
195
199
|
|
package/demo/docs.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"section": "home",
|
|
7
7
|
"order": 0,
|
|
8
8
|
"navTitle": "Home",
|
|
9
|
-
"text": "<!--{\"section\": \"home\", \"order\": 0, \"navTitle\": \"Home\"}-->\n\n# TJS Platform\n\n\n\n[playground](https://tjs-platform.web.app) | [github](https://github.com/tonioloewald/tjs-lang#readme) | [npm](https://www.npmjs.com/package/tjs-lang) | [discord](https://discord.gg/ramJ9rgky5)\n\n## What is TJS?\n\n**TJS is a language.** It's what JavaScript always promised, but never quite delivered. Indeed it's what Apple's Dylan promised and never delivered. Instead of \"a lot of the power of Lisp\", _all_ the power of Lisp. Instead of C-like Syntax, actual JavaScript syntax. Instead of easy to learn but with weird corner cases, dangerous gotchas, and problems at scale, we fix the corner cases, remove the gotchas, and provide the tools that let you scale.\n\n**TJS is also a runtime.** A runtime that remembers your function declarations and can check whether parameter types are what they ought to be. It can guarantee safety by default, and speed when it's needed (including inline WASM).\n\n**AJS is another language.** It's a language for safe evaluation with injected capabilities and a gas limit. It's the language tjs allows you to Eval and use to create a SafeFunction. It also has its own VM and runtime to allow you to build **universal endpoints**. It's a language that's easy for agents to write and comprehend. It can be converted into an AST and run remotely.\n\n**TJS is also a toolchain.** It transpiles itself into JavaScript. It transpiles TypeScript into itself and then into JS. It turns function definitions into runtime contracts, documentation, and simple tests. It uses types both as contracts and examples. It allows inline tests of private module internals that disappear at runtime. It compresses transpilation, linting, testing, and documentation generation into a single fast pass. As for bundling? It allows it but it targets an unbundled web.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│ TJS Platform │\n├─────────────────────┬─────────────────────┬─────────────────────────────┤\n│ Language │ Runtime │ Safe Execution │\n├─────────────────────┼─────────────────────┼─────────────────────────────┤\n│ TypeScript │ __tjs metadata │ AJS Agent │\n│ ↓ │ ↓ │ ↓ │\n│ TJS │ Runtime Validation │ JSON AST │\n│ ↓ │ ↓ │ ↓ │\n│ JavaScript │ Auto Documentation │ Gas-Limited VM │\n│ │ │ ↓ │\n│ │ │ Injected Capabilities │\n└─────────────────────┴─────────────────────┴─────────────────────────────┘\n```\n\n## The Problem\n\n**TypeScript is fragile.** It pretends to be a superset of JavaScript, but it isn't. It pretends to be typesafe, but it isn't. Its Turing-complete type system is harder to reason about than the code it supposedly documents—and then it all disappears at runtime.\n\nTypeScript is also _difficult to transpile_. Your browser can run entire [full virtual machines](https://infinitemac.org/) in JavaScript, but most TypeScript playgrounds either fake transpilation by stripping type declarations or use a server backend to do the real work.\n\n**JavaScript is dangerous.** `eval()` and `Function()` are so powerful they're forbidden almost everywhere—blocked by CSP in most production environments. The industry's answer? The **Container Fallacy**: shipping a 200MB Linux OS just to run a 1KB function safely. We ship buildings to deliver letters.\n\n**Security is a mess.** Every layer validates. Gateway validates. Auth validates. Business logic validates. Database validates. We spend 90% of our time building pipelines to move data to code, re-checking it at every hop.\n\n## What If?\n\nWhat if your language were:\n\n- **Honest** — types that actually exist at runtime, not fiction that evaporates\n- **Safe** — a gas-metered VM where infinite loops are impossible, no container required\n- **Mobile** — logic that travels to data, not oceans of data dragged to logic\n- **Unified** — one source of truth, not TypeScript interfaces _plus_ Zod schemas _plus_ JSDoc\n\nThat's what TJS Platform provides: **TJS** for writing your infrastructure, and **AJS** for shipping logic that runs anywhere.\n\n## TJS — Types That Survive\n\nWrite typed JavaScript where the type _is_ an example. No split-brain validation.\n\n```typescript\n// TJS: The type is an example AND a test\nfunction greet(name: 'World') -> 'Hello, World!' {\n return `Hello, ${name}!`\n}\n// At transpile time: greet('World') is called and checked against 'Hello, World!'\n\n// Runtime: The type becomes a contract\nconsole.log(greet.__tjs.params) // { name: { type: 'string', example: 'World', required: true } }\n\n// Safety: Errors are values, not crashes\nconst result = greet(123) // MonadicError: Expected string for 'greet.name', got number\n```\n\n**Why it matters:**\n\n- **One source of truth** — no more TS interfaces + Zod schemas + JSDoc comments\n- **Types as examples** — `name: 'Alice'` means \"required string, like 'Alice'\"\n- **Runtime metadata** — `__tjs` enables reflection, autocomplete, documentation from live objects\n- **Monadic errors** — type failures return values, never throw\n- **Zero build step** — transpiles in the browser, no webpack/Vite/Babel\n- **The compiler _is_ the client** — TJS transpiles itself _and_ TypeScript entirely client-side\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ COMPILE TIME │\n│ │\n│ function greet(name: 'World') ──→ Parse ──→ Extract type │\n│ name = string │\n└─────────────────────────────────────────────────────────────────┘\n ↓\n┌─────────────────────────────────────────────────────────────────┐\n│ RUNTIME │\n│ │\n│ greet.__tjs = { params: { name: { type: 'string' } } } │\n│ │\n│ greet(123) ──→ Type Check ──┬──→ Pass ──→ Execute │\n│ └──→ Fail ──→ MonadicError │\n│ (no throw) │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n## AJS — Code That Travels\n\nWrite logic that compiles to JSON and runs in a gas-limited sandbox. Send agents to data instead of shipping data to code.\n\n```typescript\nconst agent = ajs`\n function research(topic: 'AI') {\n let data = httpFetch({ url: '/search?q=' + topic })\n let summary = llmPredict({ prompt: 'Summarize: ' + data })\n return { topic, summary }\n }\n`\n\n// Run it safely—no Docker required\nconst result = await vm.run(\n agent,\n { topic: 'Agents' },\n {\n fuel: 500, // Strict CPU budget\n capabilities: { fetch: http }, // Allow ONLY http, block everything else\n }\n)\n```\n\n**Why it matters:**\n\n- **Safe eval** — run untrusted code without containers\n- **Code is JSON** — store in databases, diff, version, transmit\n- **Fuel metering** — every operation costs gas, infinite loops impossible\n- **Capability-based** — zero I/O by default, grant only what's needed\n- **LLM-native** — simple enough for small models to generate correctly\n\n## The Architecture Shift\n\n```\n❌ OLD WAY: Data-to-Code\n┌────────┐ request ┌────────┐ fetch 100 rows ┌──────────┐\n│ Client │ ────────────→ │ Server │ ───────────────→ │ Database │\n│ │ ←──────────── │ │ ←─────────────── │ │\n└────────┘ 5 rows └────────┘ 100 rows └──────────┘\n (after filtering 95 away)\n```\n\n_High latency. High bandwidth. Validate at every layer._\n\n```\n✅ TJS WAY: Code-to-Data\n┌────────┐ send agent ┌────────┐ run at data ┌──────────┐\n│ Client │ ────────────→ │ Edge │ ───────────────→ │ Database │\n│ │ ←──────────── │ │ ←─────────────── │ │\n└────────┘ 5 rows └────────┘ 5 rows └──────────┘\n (agent filtered at source)\n```\n\n_Low latency. Zero waste. Validate once._\n\nThe agent carries its own validation. The server grants capabilities. Caching happens automatically because the query _is_ the code.\n\n## Safe Eval\n\nThe holy grail: `eval()` that's actually safe.\n\n```typescript\nimport { Eval } from 'tjs-lang/eval'\n\n// Whitelist-wrapped fetch - untrusted code only reaches your domains\nconst safeFetch = (url: string) => {\n const allowed = ['api.example.com', 'cdn.example.com']\n const host = new URL(url).host\n if (!allowed.includes(host)) {\n return { error: 'Domain not allowed' }\n }\n return fetch(url)\n}\n\nconst { result, fuelUsed } = await Eval({\n code: `\n let data = fetch('https://api.example.com/products')\n return data.filter(x => x.price < budget)\n `,\n context: { budget: 100 },\n fuel: 1000,\n capabilities: { fetch: safeFetch }, // Only whitelisted domains\n})\n```\n\nThe untrusted code thinks it has `fetch`, but it only has _your_ `fetch`. No CSP violations. No infinite loops. No access to anything you didn't explicitly grant.\n\n```\n ┌─────────────────┐\n │ Untrusted Code │\n └────────┬────────┘\n │\n ┌───────────┬───────┴───────┬───────────┐\n ↓ ↓ ↓ ↓\n fetch() fs.read() loop console\n │ │ │ │\n ↓ ↓ ↓ ↓\n ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐\n │Granted? │ │Granted? │ │Fuel left│ │Granted? │\n └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘\n Y │ N N │ Y │ N N │\n ↓ ↓ ↓ ↓ ↓ ↓\n ┌────────┐ ┌───────┐ Continue Halt Block\n │ Your │ │ Block │ │\n │safeFetch│ └───────┘ ↓\n └────────┘\n```\n\n## Quick Start\n\n```bash\nnpm install tjs-lang\n```\n\n### Run an Agent\n\n```typescript\nimport { ajs, AgentVM } from 'tjs-lang'\n\nconst agent = ajs`\n function double(value: 21) {\n return { result: value * 2 }\n }\n`\n\nconst vm = new AgentVM()\nconst { result } = await vm.run(agent, { value: 21 })\nconsole.log(result) // { result: 42 }\n```\n\n### Write Typed Code\n\n```typescript\nimport { tjs } from 'tjs-lang'\n\nconst { code, metadata } = tjs`\n function add(a: 0, b: 0) -> 0 {\n return a + b\n }\n`\n// code: JavaScript with __tjs metadata attached\n// metadata: { add: { params: { a: { type: 'number', example: 0 }, b: { type: 'number', example: 0 } }, returns: { type: 'number' } } }\n```\n\n### Try the Playground\n\nSince TJS compiles itself, the playground is the full engine running entirely in your browser.\n\n**[tjs-platform.web.app](https://tjs-platform.web.app)**\n\n## At a Glance\n\n| | TypeScript | TJS | AJS |\n| --------------- | ------------------------------- | ------------------------------------------------ | ----------------- |\n| **Purpose** | Write your platform | Write your platform | Write your agents |\n| **Trust level** | Your code | Your code | Anyone's code |\n| **Compiles to** | JavaScript + `.d.ts` | JavaScript (with runtime checks + introspection) | JSON AST |\n| **Runs in** | Browser, Node, Bun | Browser, Node, Bun | Sandboxed VM |\n| **Types** | Static only (erased at runtime) | Examples → runtime validation | Schemas for I/O |\n| **Errors** | Exceptions | Monadic (values, not exceptions) | Monadic |\n| **Build step** | `tsc` → JS + `.d.ts` | Runs tests, builds docs, produces JS | None |\n\n> **Note:** TJS can transpile TypeScript into JS (via TJS) using `tjs convert`, giving your existing TS code\n> runtime type checks and introspection. You can even add inline tests using `/*test ...*/` comments\n> that run automatically during the build.\n\n## Bundle Size\n\nThe cost of \"safe eval\"—compare to a 200MB Docker image:\n\n| Bundle | Size | Gzipped |\n| ------------------------- | ------ | --------- |\n| VM only | 42 KB | **14 KB** |\n| + Batteries (LLM, vector) | 56 KB | 19 KB |\n| + Transpiler | 89 KB | 27 KB |\n| Full (with TS support) | 180 KB | 56 KB |\n\n**Dependencies:** `acorn` (JS parser), `tosijs-schema` (validation). Both have zero transitive dependencies.\n\n## Documentation\n\n- **[TJS Language Guide](DOCS-TJS.md)** — Types, syntax, runtime\n- **[AJS Runtime Guide](DOCS-AJS.md)** — VM, atoms, capabilities\n- **[Architecture Deep Dive](CONTEXT.md)** — How it all fits together\n- **[Playground](https://tjs-platform.web.app)** — Try it now\n\n## Installation\n\n```bash\n# npm\nnpm install tjs-lang\n\n# bun\nbun add tjs-lang\n\n# pnpm\npnpm add tjs-lang\n```\n\n## License\n\nApache 2.0\n"
|
|
9
|
+
"text": "<!--{\"section\": \"home\", \"order\": 0, \"navTitle\": \"Home\"}-->\n\n# TJS Platform\n\n\n\n[playground](https://tjs-platform.web.app) | [github](https://github.com/tonioloewald/tjs-lang#readme) | [npm](https://www.npmjs.com/package/tjs-lang) | [discord](https://discord.gg/ramJ9rgky5)\n\n## What is TJS?\n\n**TJS is a language.** It's what JavaScript always promised, but never quite delivered. Indeed it's what Apple's Dylan promised and never delivered. Instead of \"a lot of the power of Lisp\", _all_ the power of Lisp. Instead of C-like Syntax, actual JavaScript syntax. Instead of easy to learn but with weird corner cases, dangerous gotchas, and problems at scale, we fix the corner cases, remove the gotchas, and provide the tools that let you scale.\n\n**TJS is also a runtime.** A runtime that remembers your function declarations and can check whether parameter types are what they ought to be. It can guarantee safety by default, and speed when it's needed (including inline WASM).\n\n**AJS is another language.** It's a language for safe evaluation with injected capabilities and a gas limit. It's the language tjs allows you to Eval and use to create a SafeFunction. It also has its own VM and runtime to allow you to build **universal endpoints**. It's a language that's easy for agents to write and comprehend. It can be converted into an AST and run remotely.\n\n**TJS is also a toolchain.** It transpiles itself into JavaScript. It transpiles TypeScript into itself and then into JS. It turns function definitions into runtime contracts, documentation, and simple tests. It uses types both as contracts and examples. It allows inline tests of private module internals that disappear at runtime. It compresses transpilation, linting, testing, and documentation generation into a single fast pass. As for bundling? It allows it but it targets an unbundled web.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│ TJS Platform │\n├─────────────────────┬─────────────────────┬─────────────────────────────┤\n│ Language │ Runtime │ Safe Execution │\n├─────────────────────┼─────────────────────┼─────────────────────────────┤\n│ TypeScript │ __tjs metadata │ AJS Agent │\n│ ↓ │ ↓ │ ↓ │\n│ TJS │ Runtime Validation │ JSON AST │\n│ ↓ │ ↓ │ ↓ │\n│ JavaScript │ Auto Documentation │ Gas-Limited VM │\n│ │ │ ↓ │\n│ │ │ Injected Capabilities │\n└─────────────────────┴─────────────────────┴─────────────────────────────┘\n```\n\n## The Problem\n\n**TypeScript is fragile.** It pretends to be a superset of JavaScript, but it isn't. It pretends to be typesafe, but it isn't. Its Turing-complete type system is harder to reason about than the code it supposedly documents—and then it all disappears at runtime.\n\nTypeScript is also _difficult to transpile_. Your browser can run entire [full virtual machines](https://infinitemac.org/) in JavaScript, but most TypeScript playgrounds either fake transpilation by stripping type declarations or use a server backend to do the real work.\n\n**JavaScript is dangerous.** `eval()` and `Function()` are so powerful they're forbidden almost everywhere—blocked by CSP in most production environments. The industry's answer? The **Container Fallacy**: shipping a 200MB Linux OS just to run a 1KB function safely. We ship buildings to deliver letters.\n\n**Security is a mess.** Every layer validates. Gateway validates. Auth validates. Business logic validates. Database validates. We spend 90% of our time building pipelines to move data to code, re-checking it at every hop.\n\n## What If?\n\nWhat if your language were:\n\n- **Honest** — types that actually exist at runtime, not fiction that evaporates\n- **Safe** — a gas-metered VM where infinite loops are impossible, no container required\n- **Mobile** — logic that travels to data, not oceans of data dragged to logic\n- **Unified** — one source of truth, not TypeScript interfaces _plus_ Zod schemas _plus_ JSDoc\n\nThat's what TJS Platform provides: **TJS** for writing your infrastructure, and **AJS** for shipping logic that runs anywhere.\n\n## TJS — Types That Survive\n\nWrite typed JavaScript where the type _is_ an example. No split-brain validation.\n\n```typescript\n// TJS: The type is an example AND a test\nfunction greet(name: 'World') -> 'Hello, World!' {\n return `Hello, ${name}!`\n}\n// At transpile time: greet('World') is called and checked against 'Hello, World!'\n\n// Runtime: The type becomes a contract\nconsole.log(greet.__tjs.params) // { name: { type: 'string', example: 'World', required: true } }\n\n// Safety: Errors are values, not crashes\nconst result = greet(123) // MonadicError: Expected string for 'greet.name', got number\n```\n\n**Why it matters:**\n\n- **One source of truth** — no more TS interfaces + Zod schemas + JSDoc comments\n- **Types as examples** — `name: 'Alice'` means \"required string, like 'Alice'\"\n- **Runtime metadata** — `__tjs` enables reflection, autocomplete, documentation from live objects\n- **Monadic errors** — type failures return values, never throw\n- **Zero build step** — transpiles in the browser, no webpack/Vite/Babel\n- **The compiler _is_ the client** — TJS transpiles itself _and_ TypeScript entirely client-side\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ COMPILE TIME │\n│ │\n│ function greet(name: 'World') ──→ Parse ──→ Extract type │\n│ name = string │\n└─────────────────────────────────────────────────────────────────┘\n ↓\n┌─────────────────────────────────────────────────────────────────┐\n│ RUNTIME │\n│ │\n│ greet.__tjs = { params: { name: { type: 'string' } } } │\n│ │\n│ greet(123) ──→ Type Check ──┬──→ Pass ──→ Execute │\n│ └──→ Fail ──→ MonadicError │\n│ (no throw) │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n## AJS — Code That Travels\n\nWrite logic that compiles to JSON and runs in a gas-limited sandbox. Send agents to data instead of shipping data to code.\n\n```typescript\nconst agent = ajs`\n function research(topic: 'AI') {\n let data = httpFetch({ url: '/search?q=' + topic })\n let summary = llmPredict({ prompt: 'Summarize: ' + data })\n return { topic, summary }\n }\n`\n\n// Run it safely—no Docker required\nconst result = await vm.run(\n agent,\n { topic: 'Agents' },\n {\n fuel: 500, // Strict CPU budget\n capabilities: { fetch: http }, // Allow ONLY http, block everything else\n }\n)\n```\n\n**Why it matters:**\n\n- **Safe eval** — run untrusted code without containers\n- **Code is JSON** — store in databases, diff, version, transmit\n- **Fuel metering** — every operation costs gas, infinite loops impossible\n- **Capability-based** — zero I/O by default, grant only what's needed\n- **LLM-native** — simple enough for small models to generate correctly\n\n## The Architecture Shift\n\n```\n❌ OLD WAY: Data-to-Code\n┌────────┐ request ┌────────┐ fetch 100 rows ┌──────────┐\n│ Client │ ────────────→ │ Server │ ───────────────→ │ Database │\n│ │ ←──────────── │ │ ←─────────────── │ │\n└────────┘ 5 rows └────────┘ 100 rows └──────────┘\n (after filtering 95 away)\n```\n\n_High latency. High bandwidth. Validate at every layer._\n\n```\n✅ TJS WAY: Code-to-Data\n┌────────┐ send agent ┌────────┐ run at data ┌──────────┐\n│ Client │ ────────────→ │ Edge │ ───────────────→ │ Database │\n│ │ ←──────────── │ │ ←─────────────── │ │\n└────────┘ 5 rows └────────┘ 5 rows └──────────┘\n (agent filtered at source)\n```\n\n_Low latency. Zero waste. Validate once._\n\nThe agent carries its own validation. The server grants capabilities. Caching happens automatically because the query _is_ the code.\n\n## Safe Eval\n\nThe holy grail: `eval()` that's actually safe.\n\n```typescript\nimport { Eval } from 'tjs-lang/eval'\n\n// Whitelist-wrapped fetch - untrusted code only reaches your domains\nconst safeFetch = (url: string) => {\n const allowed = ['api.example.com', 'cdn.example.com']\n const host = new URL(url).host\n if (!allowed.includes(host)) {\n return { error: 'Domain not allowed' }\n }\n return fetch(url)\n}\n\nconst { result, fuelUsed } = await Eval({\n code: `\n let data = fetch('https://api.example.com/products')\n return data.filter(x => x.price < budget)\n `,\n context: { budget: 100 },\n fuel: 1000,\n capabilities: { fetch: safeFetch }, // Only whitelisted domains\n})\n```\n\nThe untrusted code thinks it has `fetch`, but it only has _your_ `fetch`. No CSP violations. No infinite loops. No access to anything you didn't explicitly grant.\n\n```\n ┌─────────────────┐\n │ Untrusted Code │\n └────────┬────────┘\n │\n ┌───────────┬───────┴───────┬───────────┐\n ↓ ↓ ↓ ↓\n fetch() fs.read() loop console\n │ │ │ │\n ↓ ↓ ↓ ↓\n ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐\n │Granted? │ │Granted? │ │Fuel left│ │Granted? │\n └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘\n Y │ N N │ Y │ N N │\n ↓ ↓ ↓ ↓ ↓ ↓\n ┌────────┐ ┌───────┐ Continue Halt Block\n │ Your │ │ Block │ │\n │safeFetch│ └───────┘ ↓\n └────────┘\n```\n\n## Quick Start\n\n```bash\nnpm install tjs-lang\n```\n\n### Run an Agent\n\n```typescript\nimport { ajs, AgentVM } from 'tjs-lang'\n\nconst agent = ajs`\n function double(value: 21) {\n return { result: value * 2 }\n }\n`\n\nconst vm = new AgentVM()\nconst { result } = await vm.run(agent, { value: 21 })\nconsole.log(result) // { result: 42 }\n```\n\n### Write Typed Code\n\n```typescript\nimport { tjs } from 'tjs-lang'\n\nconst { code, metadata } = tjs`\n function add(a: 0, b: 0) -> 0 {\n return a + b\n }\n`\n// code: JavaScript with __tjs metadata attached\n// metadata: { add: { params: { a: { type: 'number', example: 0 }, b: { type: 'number', example: 0 } }, returns: { type: 'number' } } }\n```\n\n### Try the Playground\n\nSince TJS compiles itself, the playground is the full engine running entirely in your browser.\n\n**[tjs-platform.web.app](https://tjs-platform.web.app)**\n\n## At a Glance\n\n| | TypeScript | TJS | AJS |\n| --------------- | ------------------------------- | ------------------------------------------------ | ----------------- |\n| **Purpose** | Write your platform | Write your platform | Write your agents |\n| **Trust level** | Your code | Your code | Anyone's code |\n| **Compiles to** | JavaScript + `.d.ts` | JavaScript (with runtime checks + introspection) | JSON AST |\n| **Runs in** | Browser, Node, Bun | Browser, Node, Bun | Sandboxed VM |\n| **Types** | Static only (erased at runtime) | Examples → runtime validation | Schemas for I/O |\n| **Errors** | Exceptions | Monadic (values, not exceptions) | Monadic |\n| **Build step** | `tsc` → JS + `.d.ts` | Runs tests, builds docs, produces JS | None |\n\n> **Note:** TJS can transpile TypeScript into JS (via TJS) using `tjs convert`, giving your existing TS code\n> runtime type checks and introspection. You can even add inline tests using `/*test ...*/` comments\n> that run automatically during the build.\n\n## Bundle Size\n\nThe cost of \"safe eval\"—compare to a 200MB Docker image:\n\n| Bundle | Size | Gzipped |\n| ------------------------- | ------ | --------- |\n| VM only | 218 KB | **66 KB** |\n| + Batteries (LLM, vector) | 14 KB | 5 KB |\n| + Transpiler | 5 KB | 2 KB |\n| Full (with TS support) | 230 KB | 71 KB |\n\n**Dependencies:** `acorn` (JS parser), `tosijs-schema` (validation). Both have zero transitive dependencies.\n\n## Documentation\n\n- **[TJS Language Guide](DOCS-TJS.md)** — Types, syntax, runtime\n- **[AJS Runtime Guide](DOCS-AJS.md)** — VM, atoms, capabilities\n- **[Architecture Deep Dive](CONTEXT.md)** — How it all fits together\n- **[Playground](https://tjs-platform.web.app)** — Try it now\n\n## Installation\n\n```bash\n# npm\nnpm install tjs-lang\n\n# bun\nbun add tjs-lang\n\n# pnpm\npnpm add tjs-lang\n```\n\n## License\n\nApache 2.0\n"
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"title": "Applied Laziness: The \"Zero-Infrastructure\" Stack",
|
|
@@ -655,7 +655,7 @@
|
|
|
655
655
|
"title": "CLAUDE.md",
|
|
656
656
|
"filename": "CLAUDE.md",
|
|
657
657
|
"path": "CLAUDE.md",
|
|
658
|
-
"text": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**tjs-lang** (npm: `tjs-lang`) is a type-safe virtual machine (~33KB) for safe execution of untrusted code in any JavaScript environment. It compiles logic chains and AI agents to JSON-serializable ASTs that run sandboxed with fuel (gas) limits.\n\nKey concept: Code travels to data (rather than shipping data to code). Agents are defined as data, not deployed code.\n\n**Two languages in one platform:**\n\n- **TJS** — TypeScript-like syntax with runtime type validation for writing your platform\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution\n\n## Common Commands\n\n```bash\n# Development\nnpm run format # ESLint fix + Prettier\nnpm run test:fast # Core tests (skips LLM & benchmarks)\nnpm run make # Full build (clean, format, grammars, tsc, esbuild)\nnpm run dev # Development server with file watcher\nnpm run start # Build demo + start dev server\nnpm run latest # Clean reinstall (rm node_modules + bun install)\n\n# Testing (framework: bun:test — describe/it/expect)\nbun test # Full test suite\nbun test src/path/to/file.test.ts # Single test file\nbun test --test-name-pattern \"pattern\" # Run tests matching pattern\nSKIP_LLM_TESTS=1 bun test # Skip LLM integration tests\nbun test --coverage # With coverage report\n\n# Efficient test debugging - capture once, query multiple times\nbun test 2>&1 | tee /tmp/test-results.txt | tail -20 # Run and save\ngrep -E \"^\\(fail\\)\" /tmp/test-results.txt # List failures\ngrep -A10 \"test name\" /tmp/test-results.txt # See specific error\n\n# CLI tools\nbun src/cli/tjs.ts check <file> # Parse and type check TJS file\nbun src/cli/tjs.ts run <file> # Transpile and execute\nbun src/cli/tjs.ts types <file> # Output type metadata as JSON\nbun src/cli/tjs.ts emit <file> # Output transpiled JavaScript\nbun src/cli/tjs.ts convert <file> # Convert TypeScript to TJS (--emit-tjs) or JS\nbun src/cli/tjs.ts test <file> # Run inline tests in a TJS file\n\n# Type checking & other\nnpm run typecheck # tsc --noEmit (type check without emitting)\nnpm run test:llm # LM Studio integration tests\nnpm run bench # Vector search benchmarks\nnpm run docs # Generate documentation\n\n# Build standalone CLI binaries\nnpm run build:cli # Compiles tjs + tjsx to dist/\n\n# Deployment (Firebase)\nnpm run deploy # Build demo + deploy functions + hosting\nnpm run deploy:hosting # Hosting only (serves from .demo/)\nnpm run functions:deploy # Cloud functions only\nnpm run functions:serve # Local functions emulator\n```\n\n## Architecture\n\n### Two-Layer Design\n\n1. **Builder Layer** (`src/builder.ts`): Fluent API that constructs AST nodes. Contains no execution logic.\n2. **Runtime Layer** (`src/vm/runtime.ts`): Executes AST nodes. Contains all atom implementations (~2900 lines, security-critical).\n\n### Key Source Files\n\n- `src/index.ts` - Main entry, re-exports everything\n- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~2900 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~226 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\n- `src/lang/emitters/ast.ts` - Emits Agent99 AST from parsed source\n- `src/lang/emitters/js.ts` - Emits JavaScript with `__tjs` metadata\n- `src/lang/emitters/from-ts.ts` - TypeScript to TJS/JS transpiler with class metadata extraction\n- `src/lang/inference.ts` - Type inference from example values\n- `src/lang/linter.ts` - Static analysis (unused vars, unreachable code, no-explicit-new)\n- `src/lang/runtime.ts` - TJS runtime (monadic errors, type checking, wrapClass)\n- `src/lang/wasm.ts` - WASM compiler (opcodes, disassembler, bytecode generation)\n- `src/types/` - Type system definitions (Type.ts, Generic.ts)\n- `src/transpiler/` - AJS transpiler (source → AST)\n- `src/batteries/` - LM Studio integration (lazy init, model audit, vector search)\n- `src/store/` - Store implementations for persistence\n- `src/rbac/` - Role-based access control\n- `src/use-cases/` - Integration tests and real-world examples (28 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert/test commands\n- `src/cli/tjsx.ts` - JSX/component runner\n- `src/cli/playground.ts` - Local playground server\n- `src/cli/create-app.ts` - Project scaffolding tool\n\n### Core APIs\n\n```typescript\n// Language\najs`...` // Parse AJS to AST\ntjs`...` // Parse TypeScript variant with type metadata\ntranspile(source, options) // Full transpilation with signature extraction\ncreateAgent(source, vm) // Creates callable agent\n\n// VM\nconst vm = new AgentVM(customAtoms)\nawait vm.run(ast, args, { fuel, capabilities, timeoutMs, trace })\n\n// Builder\nAgent.take(schema).varSet(...).httpFetch(...).return(schema)\nvm.Agent // Builder with custom atoms included\n```\n\n### Package Entry Points\n\n```typescript\nimport { Agent, AgentVM, ajs, tjs } from 'tjs-lang' // Main entry\nimport { Eval, SafeFunction } from 'tjs-lang/eval' // Safe eval utilities\nimport { tjs, transpile } from 'tjs-lang/lang' // Language tools only\nimport { fromTS } from 'tjs-lang/lang/from-ts' // TypeScript transpilation\n```\n\n### Transpiler Chain (TS → TJS → JS)\n\nTJS supports transpiling TypeScript to JavaScript with runtime type validation. The pipeline has two distinct, independently testable steps:\n\n**Step 1: TypeScript → TJS** (`fromTS`)\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\n\nconst tsSource = `\nfunction greet(name: string): string {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst result = fromTS(tsSource, { emitTJS: true })\n// result.code contains TJS:\n// function greet(name: '') -> '' {\n// return \\`Hello, \\${name}!\\`\n// }\n```\n\n**Step 2: TJS → JavaScript** (`tjs`)\n\n```typescript\nimport { tjs } from 'tosijs/lang'\n\nconst tjsSource = `\nfunction greet(name: '') -> '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsCode = tjs(tjsSource)\n// Generates JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\nimport { tjs } from 'tosijs/lang'\n\n// TypeScript source with type annotations\nconst tsSource = `\nfunction add(a: number, b: number): number {\n return a + b\n}\n`\n\n// Step 1: TS → TJS\nconst tjsResult = fromTS(tsSource, { emitTJS: true })\n\n// Step 2: TJS → JS (with runtime validation)\nconst jsCode = tjs(tjsResult.code)\n\n// Execute the result\nconst fn = new Function('__tjs', jsCode + '; return add')(__tjs_runtime)\nfn(1, 2) // Returns 3\nfn('a', 'b') // Returns { error: 'type mismatch', ... }\n```\n\n**Design Notes:**\n\n- The two steps are intentionally separate for tree-shaking (TS support is optional)\n- `fromTS` lives in a separate entry point (`tosijs/lang/from-ts`)\n- Import only what you need to keep bundle size minimal\n- Each step is independently testable (see `src/lang/codegen.test.ts`)\n\n### Security Model\n\n- **Capability-based**: VM has zero IO by default; inject `fetch`, `store`, `llm` via capabilities\n- **Fuel metering**: Every atom has a cost; execution stops when fuel exhausted\n- **Timeout enforcement**: Default `fuel × 10ms`; explicit `timeoutMs` overrides\n- **Monadic errors**: Errors wrapped in `AgentError` (VM) / `MonadicError` (TJS), not thrown (prevents exception exploits). Use `isMonadicError()` to check — `isError()` is deprecated\n- **Expression sandboxing**: ExprNode AST evaluation, blocked prototype access\n\n### Expression Evaluation\n\nExpressions use AST nodes (`$expr`), not strings:\n\n```typescript\n{ $expr: 'binary', op: '+', left: {...}, right: {...} }\n{ $expr: 'ident', name: 'varName' }\n{ $expr: 'member', object: {...}, property: 'foo' }\n```\n\nEach node costs 0.01 fuel. Forbidden: function calls, `new`, `this`, `__proto__`, `constructor`.\n\n## AJS Expression Gotchas\n\nAJS expressions behave differently from JavaScript in several important ways:\n\n- **Null member access is safe by default**: `null.foo.bar` returns `undefined` silently (uses `?.` semantics internally). This differs from JavaScript which would throw `TypeError`.\n- **No computed member access with variables**: `items[i]` fails at transpile time with \"Computed member access with variables not yet supported\". Literal indices work (`items[0]`, `obj[\"key\"]`). Workaround: use `.map`/`.reduce` atoms instead.\n- **Unknown atom errors**: When an atom doesn't exist, the error is `\"Unknown Atom: <name>\"` with no listing of available atoms.\n- **TJS parameter syntax is NOT TypeScript**: `function foo(x: 'default')` means \"required param, example value 'default'\" — not a TypeScript string literal type. LLMs consistently generate `function foo(x: string)` which is wrong. The colon value is an _example_, not a _type annotation_.\n\n## Testing Strategy\n\n- Unit tests alongside source files (`*.test.ts`)\n- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)\n- Security tests in `src/use-cases/malicious-actor.test.ts`\n- Language tests in `src/lang/lang.test.ts` (~46KB comprehensive)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\n\n## Key Patterns\n\n### Adding a New Atom\n\n1. Define with `defineAtom(opCode, inputSchema, outputSchema, implementation, { cost, timeoutMs, docs })`\n2. Add to `src/vm/atoms/` and export from `src/vm/atoms/index.ts`\n3. Add tests\n4. Run `npm run test:fast`\n\n**Atom implementation notes:**\n\n- `cost` can be static number or dynamic: `(input, ctx) => number`\n- `timeoutMs` defaults to 1000ms; use `0` for no timeout (e.g., `seq`)\n- Atoms are always async; fuel deduction is automatic in the `exec` wrapper\n\n### Debugging Agents\n\nEnable tracing: `vm.run(ast, args, { trace: true })` returns `TraceEvent[]` with execution path, fuel consumption, and state changes.\n\n### Custom Atoms Must\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access IO only via `ctx.capabilities`\n\n### Value Resolution\n\nThe `resolveValue()` function handles multiple input patterns:\n\n- `{ $kind: 'arg', path: 'varName' }` → lookup in `ctx.args`\n- `{ $expr: ... }` → evaluate ExprNode via `evaluateExpr()`\n- String with dots `'obj.foo.bar'` → traverse state with forbidden property checks\n- Bare strings → lookup in state, else return literal\n\n### Monadic Error Flow\n\nWhen `ctx.error` is set, subsequent atoms in a `seq` skip execution. Errors are wrapped in `AgentError`, not thrown. This prevents exception-based exploits.\n\n### TJS Parser Syntax Extensions\n\nTJS extends JavaScript with type annotations that survive to runtime.\n\n#### Classes (Callable Without `new`)\n\nTJS classes are wrapped to be callable without the `new` keyword:\n\n```typescript\nclass Point {\n constructor(public x: number, public y: number) {}\n}\n\n// Both work identically:\nconst p1 = Point(10, 20) // TJS way - clean\nconst p2 = new Point(10, 20) // Still works, but linter warns\n\n// The linter flags explicit `new` usage:\n// Warning: Unnecessary 'new' keyword. In TJS, classes are callable without 'new'\n```\n\nThe `wrapClass()` function in the runtime uses a Proxy to intercept calls and auto-construct.\n\n#### Function Parameters\n\n```typescript\n// Required param with example value (colon shorthand)\nfunction greet(name: 'Alice') { } // name is required, type inferred as string\n\n// Numeric type narrowing (all valid JS syntax)\nfunction calc(rate: 3.14) { } // number (float) -- has decimal point\nfunction calc(count: 42) { } // integer -- whole number\nfunction calc(index: +0) { } // non-negative integer -- + prefix\n\n// Optional param with default\nfunction greet(name = 'Alice') { } // name is optional, defaults to 'Alice'\n\n// Object parameter with shape\nfunction createUser(user: { name: '', age: 0 }) { }\n\n// Nullable type\nfunction find(id: 0 | null) { } // integer or null\n\n// Optional TS-style\nfunction greet(name?: '') { } // same as name = ''\n```\n\n#### Return Types\n\n```typescript\n// Return type annotation (arrow syntax)\nfunction add(a: 0, b: 0) -> 0 { return a + b }\n\n// Object return type\nfunction getUser(id: 0) -> { name: '', age: 0 } { ... }\n```\n\n#### Safety Markers\n\n```typescript\n// Unsafe function (skips runtime validation)\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n\n// Safe function (explicit validation)\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n\n// Unsafe block\nunsafe {\n // All calls in here skip validation\n fastPath(data)\n}\n```\n\n#### Type Declarations\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description and example\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate (auto-generates type guard from example)\nType EvenNumber {\n description: 'an even number'\n example: 2\n predicate(x) { return x % 2 === 0 }\n}\n```\n\n#### Generic Declarations\n\n```typescript\n// Simple generic\nGeneric Box<T> {\n description: 'a boxed value'\n predicate(x, T) {\n return typeof x === 'object' && x !== null && 'value' in x && T(x.value)\n }\n}\n\n// Generic with default type parameter\nGeneric Container<T, U = ''> {\n description: 'container with label'\n predicate(obj, T, U) {\n return T(obj.item) && U(obj.label)\n }\n}\n```\n\n#### Bare Assignments\n\n```typescript\n// Uppercase identifiers auto-get const\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n#### Module Safety Directive\n\n```typescript\n// At top of file - sets default validation level\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n#### TJS Mode Directives\n\nJavaScript semantics are the default. TJS improvements are opt-in via file-level directives:\n\n```typescript\nTjsStrict // Enables ALL modes below at once\n\nTjsEquals // == and != use structural equality (Is/IsNot)\nTjsClass // Classes callable without new, explicit new is banned\nTjsDate // Date is banned, use Timestamp/LegalDate instead\nTjsNoeval // eval() and new Function() are banned\nTjsStandard // Newlines as statement terminators (prevents ASI footguns)\nTjsSafeEval // Include Eval/SafeFunction in runtime for dynamic code\n```\n\nMultiple directives can be combined. Place them at the top of the file before any code.\n\n#### Equality Operators\n\nWith `TjsEquals` (or `TjsStrict`), TJS redefines equality to be structural, fixing JavaScript's confusing `==` vs `===` semantics.\n\n| Operator | Meaning | Example |\n| ----------- | -------------------------------- | --------------------------- |\n| `==` | Structural equality | `{a:1} == {a:1}` is `true` |\n| `!=` | Structural inequality | `{a:1} != {a:2}` is `true` |\n| `===` | Identity (same reference) | `obj === obj` is `true` |\n| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |\n| `a Is b` | Structural equality (explicit) | Same as `==` |\n| `a IsNot b` | Structural inequality (explicit) | Same as `!=` |\n\n```typescript\n// Structural equality - compares values deeply\nconst a = { x: 1, y: [2, 3] }\nconst b = { x: 1, y: [2, 3] }\na == b // true (same structure)\na === b // false (different objects)\n\n// Works with arrays too\n[1, 2, 3] == [1, 2, 3] // true\n\n// Infix operators for readability\nuser Is expectedUser\nresult IsNot errorValue\n```\n\n**Implementation Notes:**\n\n- **AJS (VM)**: The VM's expression evaluator (`src/vm/runtime.ts`) uses `isStructurallyEqual()` for `==`/`!=`\n- **TJS (browser/Node)**: Source transformation converts `==` to `Is()` and `!=` to `IsNot()` calls\n- **`===` and `!==`**: Always preserved as identity checks, never transformed\n- The `Is()` and `IsNot()` functions are available in `src/lang/runtime.ts` and exposed globally\n\n**Custom Equality Protocol:**\n\n- `[tjsEquals]` symbol (`Symbol.for('tjs.equals')`) — highest priority, ideal for Proxies\n- `.Equals` method — backward-compatible, works on any object/class\n- Priority: symbol → `.Equals` → structural comparison\n- `tjsEquals` is exported from `src/lang/runtime.ts` and available as `__tjs.tjsEquals`\n\n#### Polymorphic Functions\n\nMultiple function declarations with the same name are merged into a dispatcher:\n\n```typescript\nfunction area(radius: 3.14) {\n return Math.PI * radius * radius\n}\nfunction area(w: 0.0, h: 0.0) {\n return w * h\n}\n\narea(5) // dispatches to variant 1 (one number)\narea(3, 4) // dispatches to variant 2 (two numbers)\n```\n\nDispatch order: arity first, then type specificity, then declaration order. Ambiguous signatures (same types at same arity) are caught at transpile time.\n\n#### Polymorphic Constructors\n\nClasses can have multiple constructor signatures (requires `TjsClass` directive):\n\n```typescript\nTjsClass\n\nclass Point {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n constructor(coords: { x: 0.0; y: 0.0 }) {\n this.x = coords.x\n this.y = coords.y\n }\n}\n\nPoint(3, 4) // variant 1\nPoint({ x: 10, y: 20 }) // variant 2 (both produce correct instanceof)\n```\n\nThe first constructor becomes the real JS constructor; additional variants become factory functions using `Object.create`.\n\n#### Local Class Extensions\n\nAdd methods to built-in types without prototype pollution:\n\n```typescript\nextend String {\n capitalize() { return this[0].toUpperCase() + this.slice(1) }\n}\n\nextend Array {\n last() { return this[this.length - 1] }\n}\n\n'hello'.capitalize() // 'Hello' — rewritten to __ext_String.capitalize.call('hello')\n[1, 2, 3].last() // 3\n```\n\n- Methods are rewritten to `.call()` at transpile time for known-type receivers (zero overhead)\n- Runtime fallback via `registerExtension()`/`resolveExtension()` for unknown types\n- Arrow functions rejected (need `this` binding)\n- Multiple `extend` blocks for same type merge left-to-right\n- File-local only — no cross-module leaking\n\n## WASM Blocks\n\nTJS supports inline WebAssembly for performance-critical code. WASM blocks are compiled at transpile time and embedded as base64 in the output.\n\n### Syntax\n\n```typescript\nconst add = wasm (a: i32, b: i32) -> i32 {\n local.get $a\n local.get $b\n i32.add\n}\n```\n\n### Features\n\n- **Transpile-time compilation**: WASM bytecode is generated during transpilation, not at runtime\n- **WAT comments**: Human-readable WebAssembly Text format is included as comments above the base64\n- **Type-safe**: Parameters and return types are validated\n- **Self-contained**: Compiled WASM is embedded in output JS, no separate .wasm files needed\n\n### Output Example\n\nThe transpiler generates code like:\n\n```javascript\n/*\n * WASM Block: add\n * WAT (WebAssembly Text):\n * (func $add (param $a i32) (param $b i32) (result i32)\n * local.get 0\n * local.get 1\n * i32.add\n * )\n */\nconst add = await (async () => {\n const bytes = Uint8Array.from(atob('AGFzbQEAAAA...'), (c) => c.charCodeAt(0))\n const { instance } = await WebAssembly.instantiate(bytes)\n return instance.exports.fn\n})()\n```\n\n### SIMD Intrinsics (f32x4)\n\nWASM blocks support explicit SIMD via `f32x4_*` intrinsics:\n\n```typescript\nconst scale = wasm (arr: Float32Array, len: 0, factor: 0.0) -> 0 {\n let s = f32x4_splat(factor)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let v = f32x4_load(arr, off)\n f32x4_store(arr, off, f32x4_mul(v, s))\n }\n} fallback {\n for (let i = 0; i < len; i++) arr[i] *= factor\n}\n```\n\nAvailable: `f32x4_load`, `f32x4_store`, `f32x4_splat`, `f32x4_extract_lane`, `f32x4_replace_lane`, `f32x4_add`, `f32x4_sub`, `f32x4_mul`, `f32x4_div`, `f32x4_neg`, `f32x4_sqrt`.\n\n### Zero-Copy Arrays: `wasmBuffer()`\n\n`wasmBuffer(Constructor, length)` allocates typed arrays directly in WASM linear memory. When passed to a `wasm {}` block, these arrays are zero-copy — no marshalling overhead.\n\n```typescript\n// Allocate in WASM memory (zero-copy when passed to wasm blocks)\nconst xs = wasmBuffer(Float32Array, 50000)\n\n// Works like a normal Float32Array from JS\nxs[0] = 3.14\nfor (let i = 0; i < xs.length; i++) xs[i] = Math.random()\n\n// Zero-copy in WASM blocks — data is already in WASM memory\nfunction process(! xs: Float32Array, len: 0, delta: 0.0) {\n wasm {\n let vd = f32x4_splat(delta)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n f32x4_store(xs, off, f32x4_add(f32x4_load(xs, off), vd))\n }\n } fallback {\n for (let i = 0; i < len; i++) xs[i] += delta\n }\n}\n\n// After WASM runs, JS sees mutations immediately (same memory)\n```\n\n- Regular `Float32Array` args are copied in before and out after each WASM call\n- `wasmBuffer` arrays skip both copies (detected via `buffer === wasmMemory.buffer`)\n- Uses a bump allocator — allocations persist for program lifetime (no deallocation)\n- All WASM blocks in a file share one `WebAssembly.Memory` (64MB / 1024 pages)\n- Supports `Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n\n### Current Limitations\n\n- No imports/exports beyond the function itself\n- `wasmBuffer` allocations are permanent (bump allocator, no free)\n\n## Dependencies\n\nRuntime (shipped): `acorn` (JS parser, ~30KB), `tosijs-schema` (validation, ~5KB). Both have zero transitive dependencies.\n\n## Forbidden Properties (Security)\n\nThe following property names are blocked in expression evaluation to prevent prototype pollution:\n\n- `__proto__`, `constructor`, `prototype`\n\nThese are hardcoded in `runtime.ts` and checked during member access in `evaluateExpr()`.\n\n## Batteries System\n\nThe batteries (`src/batteries/`) provide zero-config local AI development:\n\n- **Lazy initialization**: First import audits LM Studio models (cached 24 hours)\n- **HTTPS detection**: Blocks local LLM calls from HTTPS contexts (security)\n- **Capabilities interface**: `fetch`, `store` (KV + vector), `llmBattery` (predict/embed)\n\nRegister battery atoms: `new AgentVM(batteryAtoms)` then pass `{ capabilities: batteries }` to `run()`.\n\n### Capability Key Naming\n\nThe base `Capabilities` interface (`runtime.ts`) uses `llm` with `{ predict, embed? }`, but the battery atoms access capabilities via different keys:\n\n| Capability key | Used by | Contains |\n| -------------- | -------------------------------------------------------- | -------------------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | Full `LLMCapability` (`predict` + `embed`) |\n| `vector` | `storeVectorize` | Just `{ embed }` (extracted from llmBattery) |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store ops |\n\nBoth `llmBattery` and `vector` can be `undefined`/`null` if LM Studio isn't available or HTTPS is detected.\n\n### Battery Atom Return Types\n\n- **`llmPredictBattery`**: Returns OpenAI message format `{ role?, content?, tool_calls? }` — NOT a plain string\n- **`storeVectorize`**: Returns `number[]` (embedding vector)\n- **`storeSearch`**: Returns `any[]` (matched documents)\n\n## Development Configuration\n\n### Bun Plugin\n\n`bunfig.toml` preloads `src/bun-plugin/tjs-plugin.ts` which enables importing `.tjs` files directly in bun. It also aliases `tjs-lang` to `./src/index.ts` for local development (monorepo-style resolution).\n\n### Code Style\n\n- **Prettier**: Single quotes, no semicolons, 2-space indentation, 80 char width, es5 trailing commas\n- Prefix unused variables with `_` (enforced by ESLint: `argsIgnorePattern: '^_'`)\n- `any` types are allowed (`@typescript-eslint/no-explicit-any: 0`)\n- Module type is ESM (`\"type\": \"module\"` in package.json)\n- Build output goes to `dist/` (declaration files only via `tsconfig.build.json`, bundles via `scripts/build.ts`)\n- Run `npm run format` before committing (ESLint fix + Prettier)\n\n### Firebase Deployment\n\nThe playground is hosted on Firebase (`tjs-platform.web.app`). Demo build output goes to `.demo/` (gitignored) which is the Firebase hosting root. Cloud Functions live in `functions/` with their own build process (`functions/src/*.tjs` → transpile → bundle). Firebase config: `firebase.json`, `.firebaserc`, `firestore.rules`.\n\nThe `docs/` directory contains real documentation (markdown), not build artifacts. See `docs/README.md` for the documentation index.\n\n### Additional Directories\n\n- `tjs-src/` — TJS runtime written in TJS itself (self-hosting)\n- `guides/` — Usage patterns, benchmarks, examples (`patterns.md`, `benchmarks.md`, `tjs-examples.md`)\n- `examples/` — Standalone TJS example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`)\n- `editors/` — Syntax highlighting for Monaco, CodeMirror, Ace, VSCode\n\n### Additional Documentation\n\n- `DOCS-TJS.md` — TJS language guide\n- `DOCS-AJS.md` — AJS runtime guide\n- `CONTEXT.md` — Architecture deep dive\n- `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done)\n- `PLAN.md` — Roadmap\n\n### Known Gotcha: Self-Contained Output\n\nTranspiled TJS code currently requires `globalThis.__tjs` to be set up with `createRuntime()` before execution. A `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.\n"
|
|
658
|
+
"text": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**tjs-lang** (npm: `tjs-lang`) is a typed JavaScript platform — a language, runtime, and toolchain that transpiles TypeScript and TJS to JavaScript with runtime type validation, inline WASM, monadic errors, and safe eval. It also includes AJS, a gas-metered VM for executing untrusted agent code in any JavaScript environment.\n\n**Three pillars:**\n\n- **TJS** — TypeScript-like syntax where types are examples that survive to runtime as contracts, documentation, and tests. Transpiles TS → TJS → JS in a single fast pass.\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution with fuel limits and injected capabilities. Code travels to data.\n- **Toolchain** — Compresses transpilation, linting, testing, and documentation generation into one pass. Includes inline WASM with SIMD, polymorphic dispatch, local class extensions, and a browser-based playground.\n\n## Common Commands\n\n```bash\n# Development\nnpm run format # ESLint fix + Prettier\nnpm run test:fast # Core tests (skips LLM & benchmarks)\nnpm run make # Full build (clean, format, grammars, tsc, esbuild)\nnpm run dev # Development server with file watcher\nnpm run start # Build demo + start dev server\nnpm run latest # Clean reinstall (rm node_modules + bun install)\n\n# Testing (framework: bun:test — describe/it/expect)\nbun test # Full test suite\nbun test src/path/to/file.test.ts # Single test file\nbun test --test-name-pattern \"pattern\" # Run tests matching pattern\nSKIP_LLM_TESTS=1 bun test # Skip LLM integration tests\nbun test --coverage # With coverage report\n\n# Efficient test debugging - capture once, query multiple times\nbun test 2>&1 | tee /tmp/test-results.txt | tail -20 # Run and save\ngrep -E \"^\\(fail\\)\" /tmp/test-results.txt # List failures\ngrep -A10 \"test name\" /tmp/test-results.txt # See specific error\n\n# CLI tools\nbun src/cli/tjs.ts check <file> # Parse and type check TJS file\nbun src/cli/tjs.ts run <file> # Transpile and execute\nbun src/cli/tjs.ts types <file> # Output type metadata as JSON\nbun src/cli/tjs.ts emit <file> # Output transpiled JavaScript\nbun src/cli/tjs.ts convert <file> # Convert TypeScript to TJS (--emit-tjs) or JS\nbun src/cli/tjs.ts test <file> # Run inline tests in a TJS file\n\n# Type checking & other\nnpm run typecheck # tsc --noEmit (type check without emitting)\nnpm run test:llm # LM Studio integration tests\nnpm run bench # Vector search benchmarks\nnpm run docs # Generate documentation\n\n# Build standalone CLI binaries\nnpm run build:cli # Compiles tjs + tjsx to dist/\n\n# Deployment (Firebase)\nnpm run deploy # Build demo + deploy functions + hosting\nnpm run deploy:hosting # Hosting only (serves from .demo/)\nnpm run functions:deploy # Cloud functions only\nnpm run functions:serve # Local functions emulator\n```\n\n## Architecture\n\n### Two-Layer Design\n\n1. **Builder Layer** (`src/builder.ts`): Fluent API that constructs AST nodes. Contains no execution logic.\n2. **Runtime Layer** (`src/vm/runtime.ts`): Executes AST nodes. Contains all atom implementations (~2900 lines, security-critical).\n\n### Key Source Files\n\n- `src/index.ts` - Main entry, re-exports everything\n- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~3000 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~226 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\n- `src/lang/emitters/ast.ts` - Emits Agent99 AST from parsed source\n- `src/lang/emitters/js.ts` - Emits JavaScript with `__tjs` metadata\n- `src/lang/emitters/from-ts.ts` - TypeScript to TJS/JS transpiler with class metadata extraction\n- `src/lang/inference.ts` - Type inference from example values\n- `src/lang/linter.ts` - Static analysis (unused vars, unreachable code, no-explicit-new)\n- `src/lang/runtime.ts` - TJS runtime (monadic errors, type checking, wrapClass)\n- `src/lang/wasm.ts` - WASM compiler (opcodes, disassembler, bytecode generation)\n- `src/types/` - Type system definitions (Type.ts, Generic.ts)\n- `src/transpiler/` - AJS transpiler (source → AST)\n- `src/batteries/` - LM Studio integration (lazy init, model audit, vector search)\n- `src/store/` - Store implementations for persistence\n- `src/rbac/` - Role-based access control\n- `src/use-cases/` - Integration tests and real-world examples (28 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert/test commands\n- `src/cli/tjsx.ts` - JSX/component runner\n- `src/cli/playground.ts` - Local playground server\n- `src/cli/create-app.ts` - Project scaffolding tool\n\n### Core APIs\n\n```typescript\n// Language\najs`...` // Parse AJS to AST\ntjs`...` // Parse TypeScript variant with type metadata\ntranspile(source, options) // Full transpilation with signature extraction\ncreateAgent(source, vm) // Creates callable agent\n\n// VM\nconst vm = new AgentVM(customAtoms)\nawait vm.run(ast, args, { fuel, capabilities, timeoutMs, trace })\n\n// Builder\nAgent.take(schema).varSet(...).httpFetch(...).return(schema)\nvm.Agent // Builder with custom atoms included\n```\n\n### Package Entry Points\n\n```typescript\nimport { Agent, AgentVM, ajs, tjs } from 'tjs-lang' // Main entry\nimport { Eval, SafeFunction } from 'tjs-lang/eval' // Safe eval utilities\nimport { tjs, transpile } from 'tjs-lang/lang' // Language tools only\nimport { fromTS } from 'tjs-lang/lang/from-ts' // TypeScript transpilation\n```\n\n### Transpiler Chain (TS → TJS → JS)\n\nTJS supports transpiling TypeScript to JavaScript with runtime type validation. The pipeline has two distinct, independently testable steps:\n\n**Step 1: TypeScript → TJS** (`fromTS`)\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\n\nconst tsSource = `\nfunction greet(name: string): string {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst result = fromTS(tsSource, { emitTJS: true })\n// result.code contains TJS:\n// function greet(name: '') -> '' {\n// return \\`Hello, \\${name}!\\`\n// }\n```\n\n**Step 2: TJS → JavaScript** (`tjs`)\n\n```typescript\nimport { tjs } from 'tosijs/lang'\n\nconst tjsSource = `\nfunction greet(name: '') -> '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsResult = tjs(tjsSource)\n// jsResult.code contains JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\nimport { tjs } from 'tosijs/lang'\n\n// TypeScript source with type annotations\nconst tsSource = `\nfunction add(a: number, b: number): number {\n return a + b\n}\n`\n\n// Step 1: TS → TJS\nconst tjsResult = fromTS(tsSource, { emitTJS: true })\n\n// Step 2: TJS → JS (with runtime validation)\nconst jsCode = tjs(tjsResult.code)\n\n// Execute the result\nconst fn = new Function('__tjs', jsCode + '; return add')(__tjs_runtime)\nfn(1, 2) // Returns 3\nfn('a', 'b') // Returns { error: 'type mismatch', ... }\n```\n\n**Design Notes:**\n\n- The two steps are intentionally separate for tree-shaking (TS support is optional)\n- `fromTS` lives in a separate entry point (`tosijs/lang/from-ts`)\n- Import only what you need to keep bundle size minimal\n- Each step is independently testable (see `src/lang/codegen.test.ts`)\n\n### Security Model\n\n- **Capability-based**: VM has zero IO by default; inject `fetch`, `store`, `llm` via capabilities\n- **Fuel metering**: Every atom has a cost; execution stops when fuel exhausted\n- **Timeout enforcement**: Default `fuel × 10ms`; explicit `timeoutMs` overrides\n- **Monadic errors**: Errors wrapped in `AgentError` (VM) / `MonadicError` (TJS), not thrown (prevents exception exploits). Use `isMonadicError()` to check — `isError()` is deprecated\n- **Expression sandboxing**: ExprNode AST evaluation, blocked prototype access\n\n### Expression Evaluation\n\nExpressions use AST nodes (`$expr`), not strings:\n\n```typescript\n{ $expr: 'binary', op: '+', left: {...}, right: {...} }\n{ $expr: 'ident', name: 'varName' }\n{ $expr: 'member', object: {...}, property: 'foo' }\n```\n\nEach node costs 0.01 fuel. Forbidden: function calls, `new`, `this`, `__proto__`, `constructor`.\n\n## AJS Expression Gotchas\n\nAJS expressions behave differently from JavaScript in several important ways:\n\n- **Null member access is safe by default**: `null.foo.bar` returns `undefined` silently (uses `?.` semantics internally). This differs from JavaScript which would throw `TypeError`.\n- **No computed member access with variables**: `items[i]` fails at transpile time with \"Computed member access with variables not yet supported\". Literal indices work (`items[0]`, `obj[\"key\"]`). Workaround: use `.map`/`.reduce` atoms instead.\n- **Unknown atom errors**: When an atom doesn't exist, the error is `\"Unknown Atom: <name>\"` with no listing of available atoms.\n- **TJS parameter syntax is NOT TypeScript**: `function foo(x: 'default')` means \"required param, example value 'default'\" — not a TypeScript string literal type. LLMs consistently generate `function foo(x: string)` which is wrong. The colon value is an _example_, not a _type annotation_.\n\n## Testing Strategy\n\n- Unit tests alongside source files (`*.test.ts`)\n- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)\n- Security tests in `src/use-cases/malicious-actor.test.ts`\n- Language tests split across 14 files in `src/lang/` (lang.test.ts, features.test.ts, codegen.test.ts, parser.test.ts, from-ts.test.ts, wasm.test.ts, etc.)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\n\n**Bug fix rule:** Always create a reproduction test case before fixing a bug.\n\n## Key Patterns\n\n### Adding a New Atom\n\n1. Define with `defineAtom(opCode, inputSchema, outputSchema, implementation, { cost, timeoutMs, docs })`\n2. Add to `src/vm/atoms/` and export from `src/vm/atoms/index.ts`\n3. Add tests\n4. Run `npm run test:fast`\n\n**Atom implementation notes:**\n\n- `cost` can be static number or dynamic: `(input, ctx) => number`\n- `timeoutMs` defaults to 1000ms; use `0` for no timeout (e.g., `seq`)\n- Atoms are always async; fuel deduction is automatic in the `exec` wrapper\n\n### Debugging Agents\n\nEnable tracing: `vm.run(ast, args, { trace: true })` returns `TraceEvent[]` with execution path, fuel consumption, and state changes.\n\n### Custom Atoms Must\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access IO only via `ctx.capabilities`\n\n### Value Resolution\n\nThe `resolveValue()` function handles multiple input patterns:\n\n- `{ $kind: 'arg', path: 'varName' }` → lookup in `ctx.args`\n- `{ $expr: ... }` → evaluate ExprNode via `evaluateExpr()`\n- String with dots `'obj.foo.bar'` → traverse state with forbidden property checks\n- Bare strings → lookup in state, else return literal\n\n### Monadic Error Flow\n\nWhen `ctx.error` is set, subsequent atoms in a `seq` skip execution. Errors are wrapped in `AgentError`, not thrown. This prevents exception-based exploits.\n\n### TJS Parser Syntax Extensions\n\nTJS extends JavaScript with type annotations that survive to runtime.\n\n#### Classes (Callable Without `new`)\n\nTJS classes are wrapped to be callable without the `new` keyword:\n\n```typescript\nclass Point {\n constructor(public x: number, public y: number) {}\n}\n\n// Both work identically:\nconst p1 = Point(10, 20) // TJS way - clean\nconst p2 = new Point(10, 20) // Still works, but linter warns\n\n// The linter flags explicit `new` usage:\n// Warning: Unnecessary 'new' keyword. In TJS, classes are callable without 'new'\n```\n\nThe `wrapClass()` function in the runtime uses a Proxy to intercept calls and auto-construct.\n\n#### Function Parameters\n\n```typescript\n// Required param with example value (colon shorthand)\nfunction greet(name: 'Alice') { } // name is required, type inferred as string\n\n// Numeric type narrowing (all valid JS syntax)\nfunction calc(rate: 3.14) { } // number (float) -- has decimal point\nfunction calc(count: 42) { } // integer -- whole number\nfunction calc(index: +0) { } // non-negative integer -- + prefix\n\n// Optional param with default\nfunction greet(name = 'Alice') { } // name is optional, defaults to 'Alice'\n\n// Object parameter with shape\nfunction createUser(user: { name: '', age: 0 }) { }\n\n// Nullable type\nfunction find(id: 0 | null) { } // integer or null\n\n// Optional TS-style\nfunction greet(name?: '') { } // same as name = ''\n```\n\n#### Return Types\n\n```typescript\n// Return type annotation (arrow syntax)\nfunction add(a: 0, b: 0) -> 0 { return a + b }\n\n// Object return type\nfunction getUser(id: 0) -> { name: '', age: 0 } { ... }\n```\n\n#### Safety Markers\n\n```typescript\n// Unsafe function (skips runtime validation)\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n\n// Safe function (explicit validation)\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n\n// Unsafe block\nunsafe {\n // All calls in here skip validation\n fastPath(data)\n}\n```\n\n#### Type Declarations\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description and example\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate (auto-generates type guard from example)\nType EvenNumber {\n description: 'an even number'\n example: 2\n predicate(x) { return x % 2 === 0 }\n}\n```\n\n#### Generic Declarations\n\n```typescript\n// Simple generic\nGeneric Box<T> {\n description: 'a boxed value'\n predicate(x, T) {\n return typeof x === 'object' && x !== null && 'value' in x && T(x.value)\n }\n}\n\n// Generic with default type parameter\nGeneric Container<T, U = ''> {\n description: 'container with label'\n predicate(obj, T, U) {\n return T(obj.item) && U(obj.label)\n }\n}\n```\n\n#### Bare Assignments\n\n```typescript\n// Uppercase identifiers auto-get const\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n#### Module Safety Directive\n\n```typescript\n// At top of file - sets default validation level\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n#### TJS Mode Directives\n\nJavaScript semantics are the default. TJS improvements are opt-in via file-level directives:\n\n```typescript\nTjsStrict // Enables ALL modes below at once\n\nTjsEquals // == and != use structural equality (Is/IsNot)\nTjsClass // Classes callable without new, explicit new is banned\nTjsDate // Date is banned, use Timestamp/LegalDate instead\nTjsNoeval // eval() and new Function() are banned\nTjsStandard // Newlines as statement terminators (prevents ASI footguns)\nTjsSafeEval // Include Eval/SafeFunction in runtime for dynamic code\n```\n\nMultiple directives can be combined. Place them at the top of the file before any code.\n\n#### Equality Operators\n\nWith `TjsEquals` (or `TjsStrict`), TJS redefines equality to be structural, fixing JavaScript's confusing `==` vs `===` semantics.\n\n| Operator | Meaning | Example |\n| ----------- | -------------------------------- | --------------------------- |\n| `==` | Structural equality | `{a:1} == {a:1}` is `true` |\n| `!=` | Structural inequality | `{a:1} != {a:2}` is `true` |\n| `===` | Identity (same reference) | `obj === obj` is `true` |\n| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |\n| `a Is b` | Structural equality (explicit) | Same as `==` |\n| `a IsNot b` | Structural inequality (explicit) | Same as `!=` |\n\n```typescript\n// Structural equality - compares values deeply\nconst a = { x: 1, y: [2, 3] }\nconst b = { x: 1, y: [2, 3] }\na == b // true (same structure)\na === b // false (different objects)\n\n// Works with arrays too\n[1, 2, 3] == [1, 2, 3] // true\n\n// Infix operators for readability\nuser Is expectedUser\nresult IsNot errorValue\n```\n\n**Implementation Notes:**\n\n- **AJS (VM)**: The VM's expression evaluator (`src/vm/runtime.ts`) uses `isStructurallyEqual()` for `==`/`!=`\n- **TJS (browser/Node)**: Source transformation converts `==` to `Is()` and `!=` to `IsNot()` calls\n- **`===` and `!==`**: Always preserved as identity checks, never transformed\n- The `Is()` and `IsNot()` functions are available in `src/lang/runtime.ts` and exposed globally\n\n**Custom Equality Protocol:**\n\n- `[tjsEquals]` symbol (`Symbol.for('tjs.equals')`) — highest priority, ideal for Proxies\n- `.Equals` method — backward-compatible, works on any object/class\n- Priority: symbol → `.Equals` → structural comparison\n- `tjsEquals` is exported from `src/lang/runtime.ts` and available as `__tjs.tjsEquals`\n\n#### Polymorphic Functions\n\nMultiple function declarations with the same name are merged into a dispatcher:\n\n```typescript\nfunction area(radius: 3.14) {\n return Math.PI * radius * radius\n}\nfunction area(w: 0.0, h: 0.0) {\n return w * h\n}\n\narea(5) // dispatches to variant 1 (one number)\narea(3, 4) // dispatches to variant 2 (two numbers)\n```\n\nDispatch order: arity first, then type specificity, then declaration order. Ambiguous signatures (same types at same arity) are caught at transpile time.\n\n#### Polymorphic Constructors\n\nClasses can have multiple constructor signatures (requires `TjsClass` directive):\n\n```typescript\nTjsClass\n\nclass Point {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n constructor(coords: { x: 0.0; y: 0.0 }) {\n this.x = coords.x\n this.y = coords.y\n }\n}\n\nPoint(3, 4) // variant 1\nPoint({ x: 10, y: 20 }) // variant 2 (both produce correct instanceof)\n```\n\nThe first constructor becomes the real JS constructor; additional variants become factory functions using `Object.create`.\n\n#### Local Class Extensions\n\nAdd methods to built-in types without prototype pollution:\n\n```typescript\nextend String {\n capitalize() { return this[0].toUpperCase() + this.slice(1) }\n}\n\nextend Array {\n last() { return this[this.length - 1] }\n}\n\n'hello'.capitalize() // 'Hello' — rewritten to __ext_String.capitalize.call('hello')\n[1, 2, 3].last() // 3\n```\n\n- Methods are rewritten to `.call()` at transpile time for known-type receivers (zero overhead)\n- Runtime fallback via `registerExtension()`/`resolveExtension()` for unknown types\n- Arrow functions rejected (need `this` binding)\n- Multiple `extend` blocks for same type merge left-to-right\n- File-local only — no cross-module leaking\n\n## WASM Blocks\n\nTJS supports inline WebAssembly for performance-critical code. WASM blocks are compiled at transpile time and embedded as base64 in the output.\n\n### Syntax\n\n```typescript\nconst add = wasm (a: i32, b: i32) -> i32 {\n local.get $a\n local.get $b\n i32.add\n}\n```\n\n### Features\n\n- **Transpile-time compilation**: WASM bytecode is generated during transpilation, not at runtime\n- **WAT comments**: Human-readable WebAssembly Text format is included as comments above the base64\n- **Type-safe**: Parameters and return types are validated\n- **Self-contained**: Compiled WASM is embedded in output JS, no separate .wasm files needed\n\n### Output Example\n\nThe transpiler generates code like:\n\n```javascript\n/*\n * WASM Block: add\n * WAT (WebAssembly Text):\n * (func $add (param $a i32) (param $b i32) (result i32)\n * local.get 0\n * local.get 1\n * i32.add\n * )\n */\nconst add = await (async () => {\n const bytes = Uint8Array.from(atob('AGFzbQEAAAA...'), (c) => c.charCodeAt(0))\n const { instance } = await WebAssembly.instantiate(bytes)\n return instance.exports.fn\n})()\n```\n\n### SIMD Intrinsics (f32x4)\n\nWASM blocks support explicit SIMD via `f32x4_*` intrinsics:\n\n```typescript\nconst scale = wasm (arr: Float32Array, len: 0, factor: 0.0) -> 0 {\n let s = f32x4_splat(factor)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let v = f32x4_load(arr, off)\n f32x4_store(arr, off, f32x4_mul(v, s))\n }\n} fallback {\n for (let i = 0; i < len; i++) arr[i] *= factor\n}\n```\n\nAvailable: `f32x4_load`, `f32x4_store`, `f32x4_splat`, `f32x4_extract_lane`, `f32x4_replace_lane`, `f32x4_add`, `f32x4_sub`, `f32x4_mul`, `f32x4_div`, `f32x4_neg`, `f32x4_sqrt`.\n\n### Zero-Copy Arrays: `wasmBuffer()`\n\n`wasmBuffer(Constructor, length)` allocates typed arrays directly in WASM linear memory. When passed to a `wasm {}` block, these arrays are zero-copy — no marshalling overhead.\n\n```typescript\n// Allocate in WASM memory (zero-copy when passed to wasm blocks)\nconst xs = wasmBuffer(Float32Array, 50000)\n\n// Works like a normal Float32Array from JS\nxs[0] = 3.14\nfor (let i = 0; i < xs.length; i++) xs[i] = Math.random()\n\n// Zero-copy in WASM blocks — data is already in WASM memory\nfunction process(! xs: Float32Array, len: 0, delta: 0.0) {\n wasm {\n let vd = f32x4_splat(delta)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n f32x4_store(xs, off, f32x4_add(f32x4_load(xs, off), vd))\n }\n } fallback {\n for (let i = 0; i < len; i++) xs[i] += delta\n }\n}\n\n// After WASM runs, JS sees mutations immediately (same memory)\n```\n\n- Regular `Float32Array` args are copied in before and out after each WASM call\n- `wasmBuffer` arrays skip both copies (detected via `buffer === wasmMemory.buffer`)\n- Uses a bump allocator — allocations persist for program lifetime (no deallocation)\n- All WASM blocks in a file share one `WebAssembly.Memory` (64MB / 1024 pages)\n- Supports `Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n\n### Current Limitations\n\n- No imports/exports beyond the function itself\n- `wasmBuffer` allocations are permanent (bump allocator, no free)\n\n## Dependencies\n\nRuntime (shipped): `acorn` (JS parser, ~30KB), `tosijs-schema` (validation, ~5KB). Both have zero transitive dependencies.\n\n## Forbidden Properties (Security)\n\nThe following property names are blocked in expression evaluation to prevent prototype pollution:\n\n- `__proto__`, `constructor`, `prototype`\n\nThese are hardcoded in `runtime.ts` and checked during member access in `evaluateExpr()`.\n\n## Batteries System\n\nThe batteries (`src/batteries/`) provide zero-config local AI development:\n\n- **Lazy initialization**: First import audits LM Studio models (cached 24 hours)\n- **HTTPS detection**: Blocks local LLM calls from HTTPS contexts (security)\n- **Capabilities interface**: `fetch`, `store` (KV + vector), `llmBattery` (predict/embed)\n\nRegister battery atoms: `new AgentVM(batteryAtoms)` then pass `{ capabilities: batteries }` to `run()`.\n\n### Capability Key Naming\n\nThe base `Capabilities` interface (`runtime.ts`) uses `llm` with `{ predict, embed? }`, but the battery atoms access capabilities via different keys:\n\n| Capability key | Used by | Contains |\n| -------------- | -------------------------------------------------------- | -------------------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | Full `LLMCapability` (`predict` + `embed`) |\n| `vector` | `storeVectorize` | Just `{ embed }` (extracted from llmBattery) |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store ops |\n\nBoth `llmBattery` and `vector` can be `undefined`/`null` if LM Studio isn't available or HTTPS is detected.\n\n### Battery Atom Return Types\n\n- **`llmPredictBattery`**: Returns OpenAI message format `{ role?, content?, tool_calls? }` — NOT a plain string\n- **`storeVectorize`**: Returns `number[]` (embedding vector)\n- **`storeSearch`**: Returns `any[]` (matched documents)\n\n## Development Configuration\n\n### Bun Plugin\n\n`bunfig.toml` preloads `src/bun-plugin/tjs-plugin.ts` which enables importing `.tjs` files directly in bun. It also aliases `tjs-lang` to `./src/index.ts` for local development (monorepo-style resolution).\n\n### Code Style\n\n- **Prettier**: Single quotes, no semicolons, 2-space indentation, 80 char width, es5 trailing commas\n- Prefix unused variables with `_` (enforced by ESLint: `argsIgnorePattern: '^_'`)\n- `any` types are allowed (`@typescript-eslint/no-explicit-any: 0`)\n- Module type is ESM (`\"type\": \"module\"` in package.json)\n- Build output goes to `dist/` (declaration files only via `tsconfig.build.json`, bundles via `scripts/build.ts`)\n- Run `npm run format` before committing (ESLint fix + Prettier)\n\n### Firebase Deployment\n\nThe playground is hosted on Firebase (`tjs-platform.web.app`). Demo build output goes to `.demo/` (gitignored) which is the Firebase hosting root. Cloud Functions live in `functions/` with their own build process (`functions/src/*.tjs` → transpile → bundle). Firebase config: `firebase.json`, `.firebaserc`, `firestore.rules`.\n\nThe `docs/` directory contains real documentation (markdown), not build artifacts. See `docs/README.md` for the documentation index.\n\n### Additional Directories\n\n- `tjs-src/` — TJS runtime written in TJS itself (self-hosting)\n- `guides/` — Usage patterns, benchmarks, examples (`patterns.md`, `benchmarks.md`, `tjs-examples.md`)\n- `examples/` — Standalone TJS example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`)\n- `editors/` — Syntax highlighting for Monaco, CodeMirror, Ace, VSCode\n\n### Additional Documentation\n\n- `DOCS-TJS.md` — TJS language guide\n- `DOCS-AJS.md` — AJS runtime guide\n- `CONTEXT.md` — Architecture deep dive\n- `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done). **Critical**: work is NOT complete until `git push` succeeds; use `bd ready` to find work, `bd close <id>` to complete\n- `PLAN.md` — Roadmap\n\n### Known Gotcha: `tjs()` Returns an Object, Not a String\n\n`tjs(source)` returns `{ code, types, metadata, testResults, ... }`. Use `.code` to get the transpiled JavaScript string. This is a common mistake.\n\n### Known Gotcha: Running Emitted TJS Code in Tests\n\nTranspiled TJS code requires `globalThis.__tjs` to be set up with `createRuntime()` before execution:\n\n```typescript\nimport { createRuntime } from '../lang/runtime'\n\nconst saved = globalThis.__tjs\nglobalThis.__tjs = createRuntime()\ntry {\n const fn = new Function(result.code + '\\nreturn fnName')()\n // ... test fn\n} finally {\n globalThis.__tjs = saved\n}\n```\n\nA `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.\n"
|
|
659
659
|
},
|
|
660
660
|
{
|
|
661
661
|
"title": "Context: Working with tosijs-schema",
|
|
@@ -675,12 +675,24 @@
|
|
|
675
675
|
"filename": "docs.test.ts",
|
|
676
676
|
"path": "src/lang/docs.test.ts"
|
|
677
677
|
},
|
|
678
|
+
{
|
|
679
|
+
"text": "Greet a person by name",
|
|
680
|
+
"title": "dts.test (inline docs)",
|
|
681
|
+
"filename": "dts.test.ts",
|
|
682
|
+
"path": "src/lang/emitters/dts.test.ts"
|
|
683
|
+
},
|
|
678
684
|
{
|
|
679
685
|
"text": "... *\\/) from source with position info.\n * These need to be preserved in TJS output in their original positions.\n * Comments inside function bodies are already preserved by the TS transpiler.\n\n---\n\n...",
|
|
680
686
|
"title": "from-ts (inline docs)",
|
|
681
687
|
"filename": "from-ts.ts",
|
|
682
688
|
"path": "src/lang/emitters/from-ts.ts"
|
|
683
689
|
},
|
|
690
|
+
{
|
|
691
|
+
"title": "GEMINI.md - Project Context & Instructions",
|
|
692
|
+
"filename": "GEMINI.md",
|
|
693
|
+
"path": "GEMINI.md",
|
|
694
|
+
"text": "# GEMINI.md - Project Context & Instructions\n\nThis file provides foundational mandates and contextual information for Gemini CLI when working in the `tjs-lang` repository.\n\n## Project Overview\n\n**tjs-lang** is a platform for type-safe JavaScript execution and AI agent orchestration. It bridges the gap between static typing and runtime safety by preserving type information and providing a secure execution environment.\n\n### Core Languages\n\n1. **TJS (Typed JavaScript):** A TypeScript-like dialect that transpiles to JavaScript with embedded `__tjs` metadata. This metadata enables runtime type validation, auto-documentation, and introspection.\n2. **AJS (Agent JavaScript):** A safe, sandboxed language for AI agents. It compiles to a JSON-serializable AST and executes in a gas-limited VM with capability-based security.\n\n### Architecture\n\n- **Builder Layer** (`src/builder.ts`): Fluent API for constructing AST nodes.\n- **Runtime Layer** (`src/vm/runtime.ts`): Security-critical core that executes AST nodes and manages fuel/capabilities.\n- **Lang Layer** (`src/lang/`): Contains the parser (based on Acorn), linter, and emitters for TJS and AJS.\n- **Batteries** (`src/batteries/`): Built-in capabilities like LLM integration (LM Studio), vector search, and persistence.\n\n## Development Lifecycle\n\n### Common Commands\n\n- **Install:** `bun install`\n- **Build:** `npm run make` (Format, build grammars, compile TS, and bundle)\n- **Fast Test:** `npm run test:fast` (Runs core tests, skips LLM and benchmarks)\n- **Full Test:** `bun test`\n- **Type Check:** `npm run typecheck`\n- **Lint:** `npm run lint`\n- **Format:** `npm run format` (ESLint fix + Prettier)\n- **Dev Server:** `npm run dev`\n- **CLI Tool:** `bun src/cli/tjs.ts <command>` (Commands: `check`, `run`, `types`, `emit`, `convert`, `test`)\n\n### Testing Strategy\n\n- **Unit Tests:** Located alongside source files (e.g., `src/lang/parser.test.ts`).\n- **Integration Tests:** Located in `src/use-cases/`.\n- **Security Tests:** Critical for the VM, found in `src/use-cases/malicious-actor.test.ts`.\n- **Reproduction:** Always create a reproduction test case before fixing a bug.\n\n## Engineering Standards & Conventions\n\n### Coding Style\n\n- **Formatting:** Prettier (Single quotes, no semicolons, 2-space indentation, 80 char width).\n- **Naming:** Follow existing conventions; prefix unused variables with `_`.\n- **Type Safety:** Prioritize TypeScript for all core logic. `any` is allowed but should be used judiciously.\n- **Security:** Rigorously protect the `src/vm/runtime.ts` file as it is security-critical. Avoid prototype access (`__proto__`, `constructor`, `prototype`) in expression evaluation.\n\n### Language Specifics\n\n- **TJS Types:** In TJS, colons indicate _examples_, not just type annotations (e.g., `name: 'Alice'` means a required string).\n- **AJS Expressions:** Be aware that AJS expressions have differences from JS (e.g., safe null member access, limited computed member access).\n- **Monadic Errors:** Prefer returning error values (Monadic errors) over throwing exceptions to ensure execution stability and security.\n\n### Documentation\n\n- Maintain `DOCS-TJS.md` and `DOCS-AJS.md` for language-specific documentation.\n- Update `CLAUDE.md` for tool-specific guidance if architectural patterns change.\n\n## Contextual Precedence\n\n- **Directives:** Explicit requests for implementation take priority.\n- **Inquiries:** Requests for analysis or advice should not result in code changes without a subsequent Directive.\n- **Security First:** Never compromise the sandboxing or fuel-metering integrity of the VM.\n"
|
|
695
|
+
},
|
|
684
696
|
{
|
|
685
697
|
"title": "icebox",
|
|
686
698
|
"filename": "tasks-f6d70f.md",
|
package/demo/index.html
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>tjs-lang |
|
|
6
|
+
<title>tjs-lang | Typed JavaScript Platform</title>
|
|
7
7
|
<meta
|
|
8
8
|
name="description"
|
|
9
|
-
content="A type
|
|
9
|
+
content="A typed JavaScript platform with runtime type validation, safe eval, inline WASM, and a gas-metered VM for untrusted code. Transpiles TypeScript and TJS to JavaScript with live metadata."
|
|
10
10
|
/>
|
|
11
11
|
|
|
12
12
|
<!-- Favicon -->
|
package/demo/src/capabilities.ts
CHANGED
|
@@ -135,12 +135,14 @@ export type LLMProvider =
|
|
|
135
135
|
| 'custom'
|
|
136
136
|
| 'openai'
|
|
137
137
|
| 'anthropic'
|
|
138
|
+
| 'gemini'
|
|
138
139
|
| 'deepseek'
|
|
139
140
|
|
|
140
141
|
export interface LLMSettings {
|
|
141
142
|
preferredProvider: LLMProvider
|
|
142
143
|
openaiKey: string
|
|
143
144
|
anthropicKey: string
|
|
145
|
+
geminiKey: string
|
|
144
146
|
deepseekKey: string
|
|
145
147
|
customLlmUrl: string
|
|
146
148
|
}
|
|
@@ -152,6 +154,7 @@ export function getSettings(): LLMSettings {
|
|
|
152
154
|
'auto') as LLMProvider,
|
|
153
155
|
openaiKey: localStorage.getItem('openaiKey') || '',
|
|
154
156
|
anthropicKey: localStorage.getItem('anthropicKey') || '',
|
|
157
|
+
geminiKey: localStorage.getItem('geminiKey') || '',
|
|
155
158
|
deepseekKey: localStorage.getItem('deepseekKey') || '',
|
|
156
159
|
customLlmUrl: localStorage.getItem('customLlmUrl') || '',
|
|
157
160
|
}
|
|
@@ -163,6 +166,7 @@ export function buildLLMCapability(settings: LLMSettings) {
|
|
|
163
166
|
preferredProvider,
|
|
164
167
|
openaiKey,
|
|
165
168
|
anthropicKey,
|
|
169
|
+
geminiKey,
|
|
166
170
|
deepseekKey,
|
|
167
171
|
customLlmUrl,
|
|
168
172
|
} = settings
|
|
@@ -171,9 +175,16 @@ export function buildLLMCapability(settings: LLMSettings) {
|
|
|
171
175
|
const hasCustomUrl = customLlmUrl && customLlmUrl.trim() !== ''
|
|
172
176
|
const hasOpenAI = openaiKey && openaiKey.trim() !== ''
|
|
173
177
|
const hasAnthropic = anthropicKey && anthropicKey.trim() !== ''
|
|
178
|
+
const hasGemini = geminiKey && geminiKey.trim() !== ''
|
|
174
179
|
const hasDeepseek = deepseekKey && deepseekKey.trim() !== ''
|
|
175
180
|
|
|
176
|
-
if (
|
|
181
|
+
if (
|
|
182
|
+
!hasCustomUrl &&
|
|
183
|
+
!hasOpenAI &&
|
|
184
|
+
!hasAnthropic &&
|
|
185
|
+
!hasGemini &&
|
|
186
|
+
!hasDeepseek
|
|
187
|
+
) {
|
|
177
188
|
return null
|
|
178
189
|
}
|
|
179
190
|
|
|
@@ -280,6 +291,33 @@ export function buildLLMCapability(settings: LLMSettings) {
|
|
|
280
291
|
return data.content?.[0]?.text ?? ''
|
|
281
292
|
}
|
|
282
293
|
|
|
294
|
+
const callGemini = async (prompt: string, options?: any): Promise<string> => {
|
|
295
|
+
const model = options?.model || 'gemini-2.0-flash'
|
|
296
|
+
const response = await fetch(
|
|
297
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${geminiKey}`,
|
|
298
|
+
{
|
|
299
|
+
method: 'POST',
|
|
300
|
+
headers: { 'Content-Type': 'application/json' },
|
|
301
|
+
body: JSON.stringify({
|
|
302
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
303
|
+
generationConfig: {
|
|
304
|
+
temperature: options?.temperature ?? 0.7,
|
|
305
|
+
},
|
|
306
|
+
}),
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
if (!response.ok) {
|
|
310
|
+
const error = await response.json().catch(() => ({}))
|
|
311
|
+
throw new Error(
|
|
312
|
+
`Gemini Error: ${response.status} - ${
|
|
313
|
+
error.error?.message || 'Check your API key'
|
|
314
|
+
}`
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
const data = await response.json()
|
|
318
|
+
return data.candidates?.[0]?.content?.parts?.[0]?.text ?? ''
|
|
319
|
+
}
|
|
320
|
+
|
|
283
321
|
const callDeepseek = async (
|
|
284
322
|
prompt: string,
|
|
285
323
|
options?: any
|
|
@@ -320,6 +358,8 @@ export function buildLLMCapability(settings: LLMSettings) {
|
|
|
320
358
|
return callOpenAI(prompt, options)
|
|
321
359
|
if (preferredProvider === 'anthropic' && hasAnthropic)
|
|
322
360
|
return callAnthropic(prompt, options)
|
|
361
|
+
if (preferredProvider === 'gemini' && hasGemini)
|
|
362
|
+
return callGemini(prompt, options)
|
|
323
363
|
if (preferredProvider === 'deepseek' && hasDeepseek)
|
|
324
364
|
return callDeepseek(prompt, options)
|
|
325
365
|
|
|
@@ -329,6 +369,7 @@ export function buildLLMCapability(settings: LLMSettings) {
|
|
|
329
369
|
custom: 'Custom Endpoint',
|
|
330
370
|
openai: 'OpenAI',
|
|
331
371
|
anthropic: 'Anthropic',
|
|
372
|
+
gemini: 'Google Gemini',
|
|
332
373
|
deepseek: 'Deepseek',
|
|
333
374
|
}
|
|
334
375
|
throw new Error(
|
|
@@ -340,6 +381,7 @@ export function buildLLMCapability(settings: LLMSettings) {
|
|
|
340
381
|
if (hasCustomUrl) return callCustom(prompt, options)
|
|
341
382
|
if (hasOpenAI) return callOpenAI(prompt, options)
|
|
342
383
|
if (hasAnthropic) return callAnthropic(prompt, options)
|
|
384
|
+
if (hasGemini) return callGemini(prompt, options)
|
|
343
385
|
if (hasDeepseek) return callDeepseek(prompt, options)
|
|
344
386
|
|
|
345
387
|
throw new Error('No LLM provider configured')
|
|
@@ -377,6 +419,7 @@ export function buildLLMBattery(settings: LLMSettings) {
|
|
|
377
419
|
preferredProvider,
|
|
378
420
|
openaiKey,
|
|
379
421
|
anthropicKey,
|
|
422
|
+
geminiKey,
|
|
380
423
|
deepseekKey,
|
|
381
424
|
customLlmUrl,
|
|
382
425
|
} = settings
|
|
@@ -384,9 +427,16 @@ export function buildLLMBattery(settings: LLMSettings) {
|
|
|
384
427
|
const hasCustomUrl = customLlmUrl && customLlmUrl.trim() !== ''
|
|
385
428
|
const hasOpenAI = openaiKey && openaiKey.trim() !== ''
|
|
386
429
|
const hasAnthropic = anthropicKey && anthropicKey.trim() !== ''
|
|
430
|
+
const hasGemini = geminiKey && geminiKey.trim() !== ''
|
|
387
431
|
const hasDeepseek = deepseekKey && deepseekKey.trim() !== ''
|
|
388
432
|
|
|
389
|
-
if (
|
|
433
|
+
if (
|
|
434
|
+
!hasCustomUrl &&
|
|
435
|
+
!hasOpenAI &&
|
|
436
|
+
!hasAnthropic &&
|
|
437
|
+
!hasGemini &&
|
|
438
|
+
!hasDeepseek
|
|
439
|
+
) {
|
|
390
440
|
return null
|
|
391
441
|
}
|
|
392
442
|
|
|
@@ -780,6 +830,59 @@ export function buildLLMBattery(settings: LLMSettings) {
|
|
|
780
830
|
return data.choices?.[0]?.message ?? { content: '' }
|
|
781
831
|
}
|
|
782
832
|
|
|
833
|
+
const callGemini = async (
|
|
834
|
+
system: string,
|
|
835
|
+
user: UserContent,
|
|
836
|
+
_tools?: any[],
|
|
837
|
+
_responseFormat?: any
|
|
838
|
+
): Promise<BatteryResult> => {
|
|
839
|
+
const model = 'gemini-2.0-flash'
|
|
840
|
+
const userText = typeof user === 'string' ? user : user.text
|
|
841
|
+
const contents: any[] = []
|
|
842
|
+
if (system) {
|
|
843
|
+
contents.push({ role: 'user', parts: [{ text: system }] })
|
|
844
|
+
contents.push({
|
|
845
|
+
role: 'model',
|
|
846
|
+
parts: [{ text: 'Understood.' }],
|
|
847
|
+
})
|
|
848
|
+
}
|
|
849
|
+
const userParts: any[] = [{ text: userText }]
|
|
850
|
+
if (typeof user !== 'string' && user.images?.length) {
|
|
851
|
+
for (const img of user.images) {
|
|
852
|
+
const match = img.match(/^data:(.*?);base64,(.*)$/)
|
|
853
|
+
if (match) {
|
|
854
|
+
userParts.push({
|
|
855
|
+
inline_data: { mime_type: match[1], data: match[2] },
|
|
856
|
+
})
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
contents.push({ role: 'user', parts: userParts })
|
|
861
|
+
|
|
862
|
+
const response = await fetch(
|
|
863
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${geminiKey}`,
|
|
864
|
+
{
|
|
865
|
+
method: 'POST',
|
|
866
|
+
headers: { 'Content-Type': 'application/json' },
|
|
867
|
+
body: JSON.stringify({
|
|
868
|
+
contents,
|
|
869
|
+
generationConfig: { temperature: 0.7 },
|
|
870
|
+
}),
|
|
871
|
+
}
|
|
872
|
+
)
|
|
873
|
+
if (!response.ok) {
|
|
874
|
+
const error = await response.json().catch(() => ({}))
|
|
875
|
+
throw new Error(
|
|
876
|
+
`Gemini Error: ${response.status} - ${
|
|
877
|
+
error.error?.message || 'Check your API key'
|
|
878
|
+
}`
|
|
879
|
+
)
|
|
880
|
+
}
|
|
881
|
+
const data = await response.json()
|
|
882
|
+
const text = data.candidates?.[0]?.content?.parts?.[0]?.text ?? ''
|
|
883
|
+
return { content: text }
|
|
884
|
+
}
|
|
885
|
+
|
|
783
886
|
return {
|
|
784
887
|
async predict(
|
|
785
888
|
system: string,
|
|
@@ -794,6 +897,8 @@ export function buildLLMBattery(settings: LLMSettings) {
|
|
|
794
897
|
return callOpenAI(system, user, tools, responseFormat)
|
|
795
898
|
if (preferredProvider === 'anthropic' && hasAnthropic)
|
|
796
899
|
return callAnthropic(system, user, tools, responseFormat)
|
|
900
|
+
if (preferredProvider === 'gemini' && hasGemini)
|
|
901
|
+
return callGemini(system, user, tools, responseFormat)
|
|
797
902
|
if (preferredProvider === 'deepseek' && hasDeepseek)
|
|
798
903
|
return callDeepseek(system, user, tools, responseFormat)
|
|
799
904
|
|
|
@@ -803,6 +908,7 @@ export function buildLLMBattery(settings: LLMSettings) {
|
|
|
803
908
|
custom: 'Custom Endpoint',
|
|
804
909
|
openai: 'OpenAI',
|
|
805
910
|
anthropic: 'Anthropic',
|
|
911
|
+
gemini: 'Google Gemini',
|
|
806
912
|
deepseek: 'Deepseek',
|
|
807
913
|
}
|
|
808
914
|
throw new Error(
|
|
@@ -815,6 +921,7 @@ export function buildLLMBattery(settings: LLMSettings) {
|
|
|
815
921
|
if (hasOpenAI) return callOpenAI(system, user, tools, responseFormat)
|
|
816
922
|
if (hasAnthropic)
|
|
817
923
|
return callAnthropic(system, user, tools, responseFormat)
|
|
924
|
+
if (hasGemini) return callGemini(system, user, tools, responseFormat)
|
|
818
925
|
if (hasDeepseek) return callDeepseek(system, user, tools, responseFormat)
|
|
819
926
|
|
|
820
927
|
throw new Error('No LLM provider configured')
|