diff --git a/Taskfile.yml b/Taskfile.yml index bef2f1b..3296184 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -88,9 +88,9 @@ tasks: npm run test # Run SDK example to verify it works - #cd {{ .ROOT_DIR }}/sdk/examples/example-js - #npm install - #npm run example + cd {{ .ROOT_DIR }}/examples + npm install + npm run example sdk:release:javascript: desc: Release javascript client SDK package diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..f4e2c6d --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.tsbuildinfo diff --git a/examples/example.js b/examples/example.js new file mode 100644 index 0000000..44193e6 --- /dev/null +++ b/examples/example.js @@ -0,0 +1,144 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +import { Client, Config, models } from "agntcy-dir"; + +function generateRecords(names) { + return names.map((name) => + JSON.parse(` +{ + "data": { + "name": "${name}", + "version": "v1.0.0", + "schema_version": "0.8.0", + "description": "My example agent", + "authors": ["AGNTCY"], + "created_at": "2025-03-19T17:06:37Z", + "skills": [ + { + "name": "natural_language_processing/natural_language_generation/text_completion", + "id": 10201 + }, + { + "name": "natural_language_processing/analytical_reasoning/problem_solving", + "id": 10702 + } + ], + "locators": [ + { + "type": "docker_image", + "url": "https://ghcr.io/agntcy/marketing-strategy" + } + ], + "domains": [ + { + "name": "technology/networking", + "id": 103 + } + ], + "modules": [ + { + "name": "integration/a2a", + "id": 203, + "data": { + "protocol_version": "lightweight orchestra moral", + "card_data": "centres", + "capabilities": [ + "state_transition_history", + "push_notifications" + ], + "transports": [ + "grpc", + "http" + ], + "output_modes": [ + "text/html" + ] + } + } + ] + } +} + `), + ); +} + +(async () => { + // Create client + const config = Config.loadFromEnv(); + let t = await Client.createGRPCTransport(config); + const client = new Client(config, t); + + // Create record objects + const records = generateRecords(["example-record", "example-record2"]); + + // Push objects + const pushed_refs = await client.push(records); + pushed_refs.forEach((ref) => { + console.log("Pushed object ref:", ref); + }); + + // Pull objects + const pulled_records = await client.pull(pushed_refs); + pulled_records.forEach((pulled_record) => { + console.log("Pulled object:", pulled_record); + }); + + // Lookup objects + const metadatas = await client.lookup(pushed_refs); + metadatas.forEach((metadata) => { + console.log("Lookup result:", metadata); + }); + + // Search objects + const search_response = await client.searchCIDs({ + queries: [ + { + type: models.search_v1.RecordQueryType.SKILL_ID, + value: "10201", + }, + ], + limit: 3, + }); + console.log("Search result:", search_response); + + // Publish objects + await client.publish({ + request: { + case: "recordRefs", + value: { + refs: pushed_refs, + }, + }, + }); + console.log("Objects published."); + + // List objects in the routing table + const list_response = await client.list({ + queries: [ + { + type: models.routing_v1.RecordQueryType.SKILL, + value: + "natural_language_processing/analytical_reasoning/problem_solving", + }, + ], + }); + list_response.forEach((r) => { + console.log("Listed objects:", r); + }); + + // Unpublish objects + await client.unpublish({ + request: { + case: "recordRefs", + value: { + refs: pushed_refs, + }, + }, + }); + console.log("Objects unpublished."); + + // Delete objects + await client.delete(pushed_refs); + console.log("Objects deleted."); +})(); diff --git a/examples/example_interactive_oidc.js b/examples/example_interactive_oidc.js new file mode 100644 index 0000000..c9efab1 --- /dev/null +++ b/examples/example_interactive_oidc.js @@ -0,0 +1,141 @@ +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +/** + * Interactive OIDC example: runs SearchCIDs only (parity with example_interactive_oidc.py). + * + * Requires DIRECTORY_CLIENT_OIDC_CLIENT_ID. Optional: DIRECTORY_CLIENT_OIDC_CLIENT_SECRET, + * DIRECTORY_CLIENT_SERVER_ADDRESS, DIRECTORY_CLIENT_TLS_SERVER_NAME, DIRECTORY_CLIENT_OIDC_REDIRECT_URI, + * DIRECTORY_CLIENT_OIDC_CALLBACK_PORT, DIRECTORY_CLIENT_OIDC_AUTH_TIMEOUT, DIRECTORY_CLIENT_AUTH_TOKEN, + * DIRECTORY_CLIENT_TLS_SKIP_VERIFY. + */ + +import { Client, Config, OAuthPkceError, TokenCache, models } from "agntcy-dir"; + +const DEFAULT_OIDC_ISSUER = "https://dev.idp.ads.outshift.io"; +const DEFAULT_SERVER_ADDRESS = "dev.gateway.ads.outshift.io:443"; +const DEFAULT_TLS_SERVER_NAME = "dev.gateway.ads.outshift.io"; +const DEFAULT_REDIRECT_URI = "http://localhost:8484/callback"; + +function requireEnv(name) { + const value = (process.env[name] ?? "").trim(); + if (!value) { + throw new Error(`${name} is required for the interactive OIDC example`); + } + return value; +} + +/** Same truthy strings as Config.loadFromEnv (DIRECTORY_CLIENT_TLS_SKIP_VERIFY). */ +function parseBoolEnv(value, defaultVal = false) { + if (value === undefined || value === "") { + return defaultVal; + } + return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase()); +} + +function parseArgs(argv) { + const out = { version: "v1*", limit: 3 }; + for (let i = 2; i < argv.length; i++) { + const a = argv[i]; + if (a === "--version" && argv[i + 1]) { + out.version = argv[++i]; + } else if (a === "--limit" && argv[i + 1]) { + const n = Number.parseInt(argv[++i], 10); + if (Number.isFinite(n)) { + out.limit = n; + } + } + } + return out; +} + +function hasUsableOidcTokenWithoutPkce() { + const authToken = (process.env.DIRECTORY_CLIENT_AUTH_TOKEN ?? "").trim(); + if (authToken) { + return true; + } + return new TokenCache().getValidToken() !== undefined; +} + +function parseOidcCallbackPort() { + const raw = process.env.DIRECTORY_CLIENT_OIDC_CALLBACK_PORT; + if (raw === undefined || raw === "") { + return Config.DEFAULT_OIDC_CALLBACK_PORT; + } + const n = Number.parseInt(raw, 10); + return Number.isFinite(n) ? n : Config.DEFAULT_OIDC_CALLBACK_PORT; +} + +function parseOidcAuthTimeout() { + const raw = process.env.DIRECTORY_CLIENT_OIDC_AUTH_TIMEOUT; + if (raw === undefined || raw === "") { + return Config.DEFAULT_OIDC_AUTH_TIMEOUT; + } + const n = Number.parseFloat(raw); + return Number.isFinite(n) ? n : Config.DEFAULT_OIDC_AUTH_TIMEOUT; +} + +function buildConfig() { + return new Config( + process.env.DIRECTORY_CLIENT_SERVER_ADDRESS ?? DEFAULT_SERVER_ADDRESS, + Config.DEFAULT_DIRCTL_PATH, + Config.DEFAULT_SPIFFE_ENDPOINT_SOCKET, + "oidc", + Config.DEFAULT_JWT_AUDIENCE, + Config.DEFAULT_TLS_CA_FILE, + Config.DEFAULT_TLS_CERT_FILE, + Config.DEFAULT_TLS_KEY_FILE, + (process.env.DIRECTORY_CLIENT_AUTH_TOKEN ?? "").trim(), + process.env.DIRECTORY_CLIENT_TLS_SERVER_NAME ?? DEFAULT_TLS_SERVER_NAME, + parseBoolEnv(process.env.DIRECTORY_CLIENT_TLS_SKIP_VERIFY, false), + DEFAULT_OIDC_ISSUER, + requireEnv("DIRECTORY_CLIENT_OIDC_CLIENT_ID"), + process.env.DIRECTORY_CLIENT_OIDC_CLIENT_SECRET ?? "", + process.env.DIRECTORY_CLIENT_OIDC_REDIRECT_URI ?? DEFAULT_REDIRECT_URI, + parseOidcCallbackPort(), + parseOidcAuthTimeout(), + undefined, + undefined, + ); +} + +async function buildClient() { + const config = buildConfig(); + const client = new Client(config); + + if (hasUsableOidcTokenWithoutPkce()) { + console.log("Using cached OIDC token."); + return client; + } + + console.log("No cached OIDC token found. Starting interactive login."); + await client.authenticateOAuthPkce(); + return client; +} + +(async () => { + try { + const args = parseArgs(process.argv); + const client = await buildClient(); + + const objects = await client.searchCIDs({ + queries: [ + { + type: models.search_v1.RecordQueryType.VERSION, + value: args.version, + }, + ], + limit: args.limit, + }); + + console.log(`SearchCIDs results for version ${JSON.stringify(args.version)}:`); + for (const obj of objects) { + console.log(obj); + } + } catch (e) { + if (e instanceof OAuthPkceError) { + console.error(`Interactive OIDC login failed: ${e.message}`); + } + throw e; + } +})(); diff --git a/examples/package-lock.json b/examples/package-lock.json new file mode 100644 index 0000000..20f061a --- /dev/null +++ b/examples/package-lock.json @@ -0,0 +1,87 @@ +{ + "name": "dir-example", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dir-example", + "version": "0.0.0", + "dependencies": { + "agntcy-dir": "file:.." + } + }, + "..": { + "name": "agntcy-dir", + "version": "1.1.0", + "license": "Apache-2.0", + "dependencies": { + "@buf/agntcy_dir.bufbuild_es": "^2.9.0-20260319131759-f3a65f0dc151.1", + "@buf/bufbuild_protovalidate.bufbuild_es": "^2.11.0-20260209202127-80ab13bee0bf.1", + "@bufbuild/protobuf": "^2.9.0", + "@connectrpc/connect": "^2.1.0", + "@connectrpc/connect-node": "^2.1.0", + "@grpc/grpc-js": "^1.13.4", + "spiffe": "^0.5.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.58.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.1", + "@types/node": "^22.19.1", + "@types/uuid": "^10.0.0", + "rollup-plugin-typescript2": "^0.37.0", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.58.0", + "uuid": "^11.1.0", + "vitest": "^3.2.4", + "workerpool": "^10.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.60.1" + } + }, + "../../dir-js": { + "name": "agntcy-dir", + "version": "1.1.0", + "extraneous": true, + "license": "Apache-2.0", + "dependencies": { + "@buf/bufbuild_protovalidate.bufbuild_es": "^2.11.0-20260209202127-80ab13bee0bf.1", + "@bufbuild/protobuf": "^2.9.0", + "@connectrpc/connect": "^2.1.0", + "@connectrpc/connect-node": "^2.1.0", + "@grpc/grpc-js": "^1.13.4", + "spiffe": "^0.5.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.58.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.1", + "@types/node": "^22.19.1", + "@types/uuid": "^10.0.0", + "rollup-plugin-typescript2": "^0.37.0", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.58.0", + "uuid": "^11.1.0", + "vitest": "^3.2.4", + "workerpool": "^10.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.60.1" + } + }, + "node_modules/agntcy-dir": { + "resolved": "..", + "link": true + } + } +} diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000..59750da --- /dev/null +++ b/examples/package.json @@ -0,0 +1,12 @@ +{ + "name": "dir-example", + "version": "0.0.0", + "type": "module", + "scripts": { + "example": "node example.js", + "example:oidc": "node example_interactive_oidc.js" + }, + "dependencies": { + "agntcy-dir": "file:.." + } +}