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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ npm install opengradient-sdk
## Requirements

- Node.js 18+ (for global `fetch`)
- A funded EVM wallet on Base (settlement happens in OPG on the Base network via [x402](https://x402.org))
- A funded EVM wallet on Base for direct `chat()` / `completion()` calls. Relay-backed `chatOhttp()` calls do not require a wallet private key.

## Quick Start

Expand Down Expand Up @@ -50,6 +50,31 @@ for await (const chunk of stream) {
}
```

### OHTTP-encrypted chat

Use `chatOhttp` when you want the request body encrypted for an OHTTP-enabled
TEE gateway through the same chat API relay used by the frontend. The default
relay paths are `/api/v1/chat/ohttp/config` and `/api/v1/chat/ohttp`.

```typescript
import { Client, TEE_LLM } from "opengradient-sdk";

const client = new Client({
ohttpRelayUrl: process.env.OG_OHTTP_RELAY_URL,
ohttpHeaders: {
Authorization: `Bearer ${process.env.OG_OHTTP_AUTH_TOKEN}`,
},
});

const result = await client.llm.chatOhttp({
model: TEE_LLM.GPT_5,
messages: [{ role: "user", content: "Hello over OHTTP" }],
});

console.log(result.chatOutput?.content);
console.log("TEE:", result.teeId);
```

### Tool / function calling

```typescript
Expand Down
30 changes: 30 additions & 0 deletions examples/llm_chat_ohttp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Run an OHTTP-encrypted chat completion through an OHTTP relay/gateway.
//
// Run with:
// OG_OHTTP_RELAY_URL=https://chat-api.example OG_OHTTP_AUTH_TOKEN=... npx ts-node examples/llm_chat_ohttp.ts

import { Client, TEE_LLM } from "../src";

async function main() {
const client = new Client({
ohttpRelayUrl: process.env.OG_OHTTP_RELAY_URL,
ohttpConfigPath: process.env.OG_OHTTP_CONFIG_PATH,
ohttpRequestPath: process.env.OG_OHTTP_REQUEST_PATH,
ohttpHeaders: process.env.OG_OHTTP_AUTH_TOKEN
? { Authorization: `Bearer ${process.env.OG_OHTTP_AUTH_TOKEN}` }
: undefined,
});

const result = await client.llm.chatOhttp({
model: TEE_LLM.GPT_5,
messages: [{ role: "user", content: "Explain OHTTP in one paragraph." }],
});

console.log(`Response: ${result.chatOutput?.content}`);
console.log(`TEE: ${result.teeId ?? "(unknown)"}`);
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
7 changes: 5 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
"testEnvironment": "node"
},
"dependencies": {
"@noble/ciphers": "^1.3.0",
"@noble/curves": "^1.8.0",
"@noble/hashes": "^1.4.0",
"@x402/core": "^2.11.0",
"@x402/evm": "^2.11.0",
"@x402/fetch": "^2.11.0",
Expand Down
47 changes: 32 additions & 15 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LLM } from "./llm";
import { OHTTPClient } from "./ohttp";
import { ClientConfig } from "./types";
import {
RegistryTEEConnection,
Expand Down Expand Up @@ -31,34 +32,50 @@ import {
*/
export class Client {
readonly llm: LLM;
readonly ohttp: OHTTPClient;

constructor(config: ClientConfig) {
const privateKey = (
config.privateKey.startsWith("0x")
? config.privateKey
: `0x${config.privateKey}`
) as `0x${string}`;
const privateKey = config.privateKey
? ((config.privateKey.startsWith("0x")
? config.privateKey
: `0x${config.privateKey}`) as `0x${string}`)
: undefined;

let connection: TEEConnection;
if (config.llmServerUrl) {
connection = new StaticTEEConnection(config.llmServerUrl);
} else {
const registry = new TEERegistry(
config.rpcUrl ?? DEFAULT_OG_RPC_URL,
config.teeRegistryAddress ?? DEFAULT_TEE_REGISTRY_ADDRESS,
);
connection = new RegistryTEEConnection(registry);
let connection: TEEConnection | undefined;
let registry: TEERegistry | undefined;
if (privateKey || !hasExplicitOHTTPRelay(config)) {
if (config.llmServerUrl) {
connection = new StaticTEEConnection(config.llmServerUrl);
} else {
registry = new TEERegistry(
config.rpcUrl ?? DEFAULT_OG_RPC_URL,
config.teeRegistryAddress ?? DEFAULT_TEE_REGISTRY_ADDRESS,
);
connection = new RegistryTEEConnection(registry);
}
}

this.ohttp = new OHTTPClient({
relayUrl: config.ohttpRelayUrl ?? config.llmServerUrl,
requestPath: config.ohttpRequestPath,
configPath: config.ohttpConfigPath,
headers: config.ohttpHeaders,
});

this.llm = new LLM({
privateKey,
maxPaymentValue: config.maxPaymentValue,
connection,
ohttp: this.ohttp,
});
}

/** Tear down dispatchers and any background refresh timers. */
async close(): Promise<void> {
await this.llm.close();
await Promise.all([this.llm.close(), this.ohttp.close()]);
}
}

function hasExplicitOHTTPRelay(config: ClientConfig): boolean {
return Boolean(config.ohttpRelayUrl);
}
18 changes: 18 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
export { Client } from "./client";
export { LLM } from "./llm";
export {
OHTTPClient,
OHTTP_CHUNKED_RESPONSE_MEDIA_TYPE,
OHTTP_REQUEST_MEDIA_TYPE,
OHTTP_RESPONSE_MEDIA_TYPE,
} from "./ohttp";
export type {
OHTTPByteStreamResult,
OHTTPClientConfig,
OHTTPGatewayConfig,
OHTTPGatewayMetadata,
OHTTPJsonResult,
OHTTPKeyConfig,
OHTTPRequestOptions,
OHTTPRouteMetadata,
OHTTPSigningKey,
} from "./ohttp";

export { TEE_LLM, X402SettlementMode, OpenGradientError } from "./types";

Expand All @@ -24,6 +41,7 @@ export {
TEE_TYPE_VALIDATOR,
} from "./teeRegistry";
export type { TEEEndpoint } from "./teeRegistry";
export type { TEEOHTTPConfig, TEEOHTTPEndpoint } from "./teeRegistry";

export {
RegistryTEEConnection,
Expand Down
Loading
Loading