初始化提交
Some checks failed
CI / Check / macos-latest (push) Has been cancelled
CI / Check / ubuntu-latest (push) Has been cancelled
CI / Check / windows-latest (push) Has been cancelled
CI / Test / macos-latest (push) Has been cancelled
CI / Test / ubuntu-latest (push) Has been cancelled
CI / Test / windows-latest (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / Secrets Scan (push) Has been cancelled
CI / Install Script Smoke Test (push) Has been cancelled
Some checks failed
CI / Check / macos-latest (push) Has been cancelled
CI / Check / ubuntu-latest (push) Has been cancelled
CI / Check / windows-latest (push) Has been cancelled
CI / Test / macos-latest (push) Has been cancelled
CI / Test / ubuntu-latest (push) Has been cancelled
CI / Test / windows-latest (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / Secrets Scan (push) Has been cancelled
CI / Install Script Smoke Test (push) Has been cancelled
This commit is contained in:
34
sdk/javascript/examples/basic.js
Normal file
34
sdk/javascript/examples/basic.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Basic example — create an agent and chat with it.
|
||||
*
|
||||
* Usage:
|
||||
* node basic.js
|
||||
*/
|
||||
|
||||
const { OpenFang } = require("../index");
|
||||
|
||||
async function main() {
|
||||
const client = new OpenFang("http://localhost:3000");
|
||||
|
||||
// Check server health
|
||||
const health = await client.health();
|
||||
console.log("Server:", health);
|
||||
|
||||
// List existing agents
|
||||
const agents = await client.agents.list();
|
||||
console.log("Agents:", agents.length);
|
||||
|
||||
// Create a new agent from the "assistant" template
|
||||
const agent = await client.agents.create({ template: "assistant" });
|
||||
console.log("Created agent:", agent.id);
|
||||
|
||||
// Send a message and get the full response
|
||||
const reply = await client.agents.message(agent.id, "What can you help me with?");
|
||||
console.log("Reply:", reply);
|
||||
|
||||
// Clean up
|
||||
await client.agents.delete(agent.id);
|
||||
console.log("Agent deleted.");
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
33
sdk/javascript/examples/streaming.js
Normal file
33
sdk/javascript/examples/streaming.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Streaming example — stream agent responses token by token.
|
||||
*
|
||||
* Usage:
|
||||
* node streaming.js
|
||||
*/
|
||||
|
||||
const { OpenFang } = require("../index");
|
||||
|
||||
async function main() {
|
||||
const client = new OpenFang("http://localhost:3000");
|
||||
|
||||
// Create an agent
|
||||
const agent = await client.agents.create({ template: "assistant" });
|
||||
console.log("Agent:", agent.id);
|
||||
|
||||
// Stream the response
|
||||
console.log("\n--- Streaming response ---");
|
||||
for await (const event of client.agents.stream(agent.id, "Tell me a short story about a robot.")) {
|
||||
if (event.type === "text_delta" && event.delta) {
|
||||
process.stdout.write(event.delta);
|
||||
} else if (event.type === "tool_call") {
|
||||
console.log("\n[Tool call:", event.tool, "]");
|
||||
} else if (event.type === "done") {
|
||||
console.log("\n--- Done ---");
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
await client.agents.delete(agent.id);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
140
sdk/javascript/index.d.ts
vendored
Normal file
140
sdk/javascript/index.d.ts
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
export class OpenFangError extends Error {
|
||||
status: number;
|
||||
body: string;
|
||||
constructor(message: string, status: number, body: string);
|
||||
}
|
||||
|
||||
export interface AgentCreateOpts {
|
||||
template?: string;
|
||||
name?: string;
|
||||
model?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface MessageOpts {
|
||||
attachments?: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface StreamEvent {
|
||||
type?: string;
|
||||
delta?: string;
|
||||
raw?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export class OpenFang {
|
||||
baseUrl: string;
|
||||
agents: AgentResource;
|
||||
sessions: SessionResource;
|
||||
workflows: WorkflowResource;
|
||||
skills: SkillResource;
|
||||
channels: ChannelResource;
|
||||
tools: ToolResource;
|
||||
models: ModelResource;
|
||||
providers: ProviderResource;
|
||||
memory: MemoryResource;
|
||||
triggers: TriggerResource;
|
||||
schedules: ScheduleResource;
|
||||
|
||||
constructor(baseUrl: string, opts?: { headers?: Record<string, string> });
|
||||
|
||||
health(): Promise<unknown>;
|
||||
healthDetail(): Promise<unknown>;
|
||||
status(): Promise<unknown>;
|
||||
version(): Promise<unknown>;
|
||||
metrics(): Promise<string>;
|
||||
usage(): Promise<unknown>;
|
||||
config(): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class AgentResource {
|
||||
list(): Promise<unknown[]>;
|
||||
get(id: string): Promise<unknown>;
|
||||
create(opts: AgentCreateOpts): Promise<{ id: string; [key: string]: unknown }>;
|
||||
delete(id: string): Promise<unknown>;
|
||||
stop(id: string): Promise<unknown>;
|
||||
clone(id: string): Promise<unknown>;
|
||||
update(id: string, data: Record<string, unknown>): Promise<unknown>;
|
||||
setMode(id: string, mode: string): Promise<unknown>;
|
||||
setModel(id: string, model: string): Promise<unknown>;
|
||||
message(id: string, text: string, opts?: MessageOpts): Promise<unknown>;
|
||||
stream(id: string, text: string, opts?: MessageOpts): AsyncGenerator<StreamEvent>;
|
||||
session(id: string): Promise<unknown>;
|
||||
resetSession(id: string): Promise<unknown>;
|
||||
compactSession(id: string): Promise<unknown>;
|
||||
listSessions(id: string): Promise<unknown[]>;
|
||||
createSession(id: string, label?: string): Promise<unknown>;
|
||||
switchSession(id: string, sessionId: string): Promise<unknown>;
|
||||
getSkills(id: string): Promise<unknown>;
|
||||
setSkills(id: string, skills: unknown): Promise<unknown>;
|
||||
upload(id: string, file: Blob | File, filename: string): Promise<unknown>;
|
||||
setIdentity(id: string, identity: Record<string, unknown>): Promise<unknown>;
|
||||
patchConfig(id: string, config: Record<string, unknown>): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class SessionResource {
|
||||
list(): Promise<unknown[]>;
|
||||
delete(id: string): Promise<unknown>;
|
||||
setLabel(id: string, label: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class WorkflowResource {
|
||||
list(): Promise<unknown[]>;
|
||||
create(workflow: Record<string, unknown>): Promise<unknown>;
|
||||
run(id: string, input?: Record<string, unknown>): Promise<unknown>;
|
||||
runs(id: string): Promise<unknown[]>;
|
||||
}
|
||||
|
||||
export class SkillResource {
|
||||
list(): Promise<unknown[]>;
|
||||
install(skill: Record<string, unknown>): Promise<unknown>;
|
||||
uninstall(skill: Record<string, unknown>): Promise<unknown>;
|
||||
search(query: string): Promise<unknown[]>;
|
||||
}
|
||||
|
||||
export class ChannelResource {
|
||||
list(): Promise<unknown[]>;
|
||||
configure(name: string, config: Record<string, unknown>): Promise<unknown>;
|
||||
remove(name: string): Promise<unknown>;
|
||||
test(name: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class ToolResource {
|
||||
list(): Promise<unknown[]>;
|
||||
}
|
||||
|
||||
export class ModelResource {
|
||||
list(): Promise<unknown[]>;
|
||||
get(id: string): Promise<unknown>;
|
||||
aliases(): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class ProviderResource {
|
||||
list(): Promise<unknown[]>;
|
||||
setKey(name: string, key: string): Promise<unknown>;
|
||||
deleteKey(name: string): Promise<unknown>;
|
||||
test(name: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class MemoryResource {
|
||||
getAll(agentId: string): Promise<Record<string, unknown>>;
|
||||
get(agentId: string, key: string): Promise<unknown>;
|
||||
set(agentId: string, key: string, value: unknown): Promise<unknown>;
|
||||
delete(agentId: string, key: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class TriggerResource {
|
||||
list(): Promise<unknown[]>;
|
||||
create(trigger: Record<string, unknown>): Promise<unknown>;
|
||||
update(id: string, trigger: Record<string, unknown>): Promise<unknown>;
|
||||
delete(id: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class ScheduleResource {
|
||||
list(): Promise<unknown[]>;
|
||||
create(schedule: Record<string, unknown>): Promise<unknown>;
|
||||
update(id: string, schedule: Record<string, unknown>): Promise<unknown>;
|
||||
delete(id: string): Promise<unknown>;
|
||||
run(id: string): Promise<unknown>;
|
||||
}
|
||||
479
sdk/javascript/index.js
Normal file
479
sdk/javascript/index.js
Normal file
@@ -0,0 +1,479 @@
|
||||
/**
|
||||
* @openfang/sdk — Official JavaScript client for the OpenFang Agent OS REST API.
|
||||
*
|
||||
* Usage:
|
||||
* const { OpenFang } = require("@openfang/sdk");
|
||||
* const client = new OpenFang("http://localhost:3000");
|
||||
*
|
||||
* const agent = await client.agents.create({ template: "assistant" });
|
||||
* const reply = await client.agents.message(agent.id, "Hello!");
|
||||
* console.log(reply);
|
||||
*
|
||||
* // Streaming:
|
||||
* for await (const event of client.agents.stream(agent.id, "Tell me a joke")) {
|
||||
* process.stdout.write(event.delta || "");
|
||||
* }
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
class OpenFangError extends Error {
|
||||
constructor(message, status, body) {
|
||||
super(message);
|
||||
this.name = "OpenFangError";
|
||||
this.status = status;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
class OpenFang {
|
||||
/**
|
||||
* @param {string} baseUrl - OpenFang server URL (e.g. "http://localhost:3000")
|
||||
* @param {object} [opts]
|
||||
* @param {Record<string, string>} [opts.headers] - Extra headers for every request
|
||||
*/
|
||||
constructor(baseUrl, opts) {
|
||||
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
||||
this._headers = Object.assign({ "Content-Type": "application/json" }, (opts && opts.headers) || {});
|
||||
this.agents = new AgentResource(this);
|
||||
this.sessions = new SessionResource(this);
|
||||
this.workflows = new WorkflowResource(this);
|
||||
this.skills = new SkillResource(this);
|
||||
this.channels = new ChannelResource(this);
|
||||
this.tools = new ToolResource(this);
|
||||
this.models = new ModelResource(this);
|
||||
this.providers = new ProviderResource(this);
|
||||
this.memory = new MemoryResource(this);
|
||||
this.triggers = new TriggerResource(this);
|
||||
this.schedules = new ScheduleResource(this);
|
||||
}
|
||||
|
||||
/** Low-level fetch wrapper. */
|
||||
async _request(method, path, body) {
|
||||
var url = this.baseUrl + path;
|
||||
var init = { method: method, headers: Object.assign({}, this._headers) };
|
||||
if (body !== undefined) {
|
||||
init.body = JSON.stringify(body);
|
||||
}
|
||||
var res = await fetch(url, init);
|
||||
if (!res.ok) {
|
||||
var text = await res.text().catch(function () { return ""; });
|
||||
throw new OpenFangError("HTTP " + res.status + ": " + text, res.status, text);
|
||||
}
|
||||
var ct = res.headers.get("content-type") || "";
|
||||
if (ct.includes("application/json")) {
|
||||
return res.json();
|
||||
}
|
||||
return res.text();
|
||||
}
|
||||
|
||||
/** Low-level SSE streaming. Returns an async iterator of parsed events. */
|
||||
async *_stream(method, path, body) {
|
||||
var url = this.baseUrl + path;
|
||||
var headers = Object.assign({}, this._headers, { Accept: "text/event-stream" });
|
||||
var init = { method: method, headers: headers };
|
||||
if (body !== undefined) {
|
||||
init.body = JSON.stringify(body);
|
||||
}
|
||||
var res = await fetch(url, init);
|
||||
if (!res.ok) {
|
||||
var text = await res.text().catch(function () { return ""; });
|
||||
throw new OpenFangError("HTTP " + res.status + ": " + text, res.status, text);
|
||||
}
|
||||
var reader = res.body.getReader();
|
||||
var decoder = new TextDecoder();
|
||||
var buffer = "";
|
||||
while (true) {
|
||||
var result = await reader.read();
|
||||
if (result.done) break;
|
||||
buffer += decoder.decode(result.value, { stream: true });
|
||||
var lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
if (line.startsWith("data: ")) {
|
||||
var data = line.slice(6);
|
||||
if (data === "[DONE]") return;
|
||||
try {
|
||||
yield JSON.parse(data);
|
||||
} catch (_) {
|
||||
yield { raw: data };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Health check. */
|
||||
async health() {
|
||||
return this._request("GET", "/api/health");
|
||||
}
|
||||
|
||||
/** Detailed health. */
|
||||
async healthDetail() {
|
||||
return this._request("GET", "/api/health/detail");
|
||||
}
|
||||
|
||||
/** Server status. */
|
||||
async status() {
|
||||
return this._request("GET", "/api/status");
|
||||
}
|
||||
|
||||
/** Server version. */
|
||||
async version() {
|
||||
return this._request("GET", "/api/version");
|
||||
}
|
||||
|
||||
/** Prometheus metrics (text). */
|
||||
async metrics() {
|
||||
return this._request("GET", "/api/metrics");
|
||||
}
|
||||
|
||||
/** Usage statistics. */
|
||||
async usage() {
|
||||
return this._request("GET", "/api/usage");
|
||||
}
|
||||
|
||||
/** Config. */
|
||||
async config() {
|
||||
return this._request("GET", "/api/config");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Agent Resource ──────────────────────────────────────────────
|
||||
|
||||
class AgentResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
/** List all agents. */
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/agents");
|
||||
}
|
||||
|
||||
/** Get agent by ID. */
|
||||
async get(id) {
|
||||
return this._c._request("GET", "/api/agents/" + id);
|
||||
}
|
||||
|
||||
/** Create (spawn) a new agent.
|
||||
* @param {object} opts - e.g. { template: "assistant", name: "My Agent" }
|
||||
*/
|
||||
async create(opts) {
|
||||
return this._c._request("POST", "/api/agents", opts);
|
||||
}
|
||||
|
||||
/** Delete (kill) an agent. */
|
||||
async delete(id) {
|
||||
return this._c._request("DELETE", "/api/agents/" + id);
|
||||
}
|
||||
|
||||
/** Stop an agent. */
|
||||
async stop(id) {
|
||||
return this._c._request("POST", "/api/agents/" + id + "/stop");
|
||||
}
|
||||
|
||||
/** Clone an agent. */
|
||||
async clone(id) {
|
||||
return this._c._request("POST", "/api/agents/" + id + "/clone");
|
||||
}
|
||||
|
||||
/** Update agent. */
|
||||
async update(id, data) {
|
||||
return this._c._request("PUT", "/api/agents/" + id + "/update", data);
|
||||
}
|
||||
|
||||
/** Set agent mode. */
|
||||
async setMode(id, mode) {
|
||||
return this._c._request("PUT", "/api/agents/" + id + "/mode", { mode: mode });
|
||||
}
|
||||
|
||||
/** Set agent model. */
|
||||
async setModel(id, model) {
|
||||
return this._c._request("PUT", "/api/agents/" + id + "/model", { model: model });
|
||||
}
|
||||
|
||||
/** Send a message and get the full response. */
|
||||
async message(id, text, opts) {
|
||||
var body = Object.assign({ message: text }, opts || {});
|
||||
return this._c._request("POST", "/api/agents/" + id + "/message", body);
|
||||
}
|
||||
|
||||
/** Send a message and stream the response (async iterator of SSE events).
|
||||
* @example
|
||||
* for await (const evt of client.agents.stream(id, "Hello")) {
|
||||
* if (evt.type === "text_delta") process.stdout.write(evt.delta);
|
||||
* }
|
||||
*/
|
||||
async *stream(id, text, opts) {
|
||||
var body = Object.assign({ message: text }, opts || {});
|
||||
yield* this._c._stream("POST", "/api/agents/" + id + "/message/stream", body);
|
||||
}
|
||||
|
||||
/** Get agent session. */
|
||||
async session(id) {
|
||||
return this._c._request("GET", "/api/agents/" + id + "/session");
|
||||
}
|
||||
|
||||
/** Reset agent session. */
|
||||
async resetSession(id) {
|
||||
return this._c._request("POST", "/api/agents/" + id + "/session/reset");
|
||||
}
|
||||
|
||||
/** Compact session. */
|
||||
async compactSession(id) {
|
||||
return this._c._request("POST", "/api/agents/" + id + "/session/compact");
|
||||
}
|
||||
|
||||
/** List sessions for an agent. */
|
||||
async listSessions(id) {
|
||||
return this._c._request("GET", "/api/agents/" + id + "/sessions");
|
||||
}
|
||||
|
||||
/** Create a new session. */
|
||||
async createSession(id, label) {
|
||||
return this._c._request("POST", "/api/agents/" + id + "/sessions", { label: label });
|
||||
}
|
||||
|
||||
/** Switch to a session. */
|
||||
async switchSession(id, sessionId) {
|
||||
return this._c._request("POST", "/api/agents/" + id + "/sessions/" + sessionId + "/switch");
|
||||
}
|
||||
|
||||
/** Get agent skills. */
|
||||
async getSkills(id) {
|
||||
return this._c._request("GET", "/api/agents/" + id + "/skills");
|
||||
}
|
||||
|
||||
/** Set agent skills. */
|
||||
async setSkills(id, skills) {
|
||||
return this._c._request("PUT", "/api/agents/" + id + "/skills", skills);
|
||||
}
|
||||
|
||||
/** Upload a file to agent. */
|
||||
async upload(id, file, filename) {
|
||||
var url = this._c.baseUrl + "/api/agents/" + id + "/upload";
|
||||
var form = new FormData();
|
||||
form.append("file", file, filename);
|
||||
var res = await fetch(url, { method: "POST", body: form });
|
||||
if (!res.ok) throw new OpenFangError("Upload failed: " + res.status, res.status);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/** Update agent identity. */
|
||||
async setIdentity(id, identity) {
|
||||
return this._c._request("PATCH", "/api/agents/" + id + "/identity", identity);
|
||||
}
|
||||
|
||||
/** Patch agent config. */
|
||||
async patchConfig(id, config) {
|
||||
return this._c._request("PATCH", "/api/agents/" + id + "/config", config);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Session Resource ────────────────────────────────────────────
|
||||
|
||||
class SessionResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/sessions");
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
return this._c._request("DELETE", "/api/sessions/" + id);
|
||||
}
|
||||
|
||||
async setLabel(id, label) {
|
||||
return this._c._request("PUT", "/api/sessions/" + id + "/label", { label: label });
|
||||
}
|
||||
}
|
||||
|
||||
// ── Workflow Resource ───────────────────────────────────────────
|
||||
|
||||
class WorkflowResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/workflows");
|
||||
}
|
||||
|
||||
async create(workflow) {
|
||||
return this._c._request("POST", "/api/workflows", workflow);
|
||||
}
|
||||
|
||||
async run(id, input) {
|
||||
return this._c._request("POST", "/api/workflows/" + id + "/run", input);
|
||||
}
|
||||
|
||||
async runs(id) {
|
||||
return this._c._request("GET", "/api/workflows/" + id + "/runs");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Skill Resource ──────────────────────────────────────────────
|
||||
|
||||
class SkillResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/skills");
|
||||
}
|
||||
|
||||
async install(skill) {
|
||||
return this._c._request("POST", "/api/skills/install", skill);
|
||||
}
|
||||
|
||||
async uninstall(skill) {
|
||||
return this._c._request("POST", "/api/skills/uninstall", skill);
|
||||
}
|
||||
|
||||
async search(query) {
|
||||
return this._c._request("GET", "/api/marketplace/search?q=" + encodeURIComponent(query));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Channel Resource ────────────────────────────────────────────
|
||||
|
||||
class ChannelResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/channels");
|
||||
}
|
||||
|
||||
async configure(name, config) {
|
||||
return this._c._request("POST", "/api/channels/" + name + "/configure", config);
|
||||
}
|
||||
|
||||
async remove(name) {
|
||||
return this._c._request("DELETE", "/api/channels/" + name + "/configure");
|
||||
}
|
||||
|
||||
async test(name) {
|
||||
return this._c._request("POST", "/api/channels/" + name + "/test");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tool Resource ───────────────────────────────────────────────
|
||||
|
||||
class ToolResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/tools");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Model Resource ──────────────────────────────────────────────
|
||||
|
||||
class ModelResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/models");
|
||||
}
|
||||
|
||||
async get(id) {
|
||||
return this._c._request("GET", "/api/models/" + id);
|
||||
}
|
||||
|
||||
async aliases() {
|
||||
return this._c._request("GET", "/api/models/aliases");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Provider Resource ───────────────────────────────────────────
|
||||
|
||||
class ProviderResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/providers");
|
||||
}
|
||||
|
||||
async setKey(name, key) {
|
||||
return this._c._request("POST", "/api/providers/" + name + "/key", { key: key });
|
||||
}
|
||||
|
||||
async deleteKey(name) {
|
||||
return this._c._request("DELETE", "/api/providers/" + name + "/key");
|
||||
}
|
||||
|
||||
async test(name) {
|
||||
return this._c._request("POST", "/api/providers/" + name + "/test");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Memory Resource ─────────────────────────────────────────────
|
||||
|
||||
class MemoryResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async getAll(agentId) {
|
||||
return this._c._request("GET", "/api/memory/agents/" + agentId + "/kv");
|
||||
}
|
||||
|
||||
async get(agentId, key) {
|
||||
return this._c._request("GET", "/api/memory/agents/" + agentId + "/kv/" + key);
|
||||
}
|
||||
|
||||
async set(agentId, key, value) {
|
||||
return this._c._request("PUT", "/api/memory/agents/" + agentId + "/kv/" + key, { value: value });
|
||||
}
|
||||
|
||||
async delete(agentId, key) {
|
||||
return this._c._request("DELETE", "/api/memory/agents/" + agentId + "/kv/" + key);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Trigger Resource ────────────────────────────────────────────
|
||||
|
||||
class TriggerResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/triggers");
|
||||
}
|
||||
|
||||
async create(trigger) {
|
||||
return this._c._request("POST", "/api/triggers", trigger);
|
||||
}
|
||||
|
||||
async update(id, trigger) {
|
||||
return this._c._request("PUT", "/api/triggers/" + id, trigger);
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
return this._c._request("DELETE", "/api/triggers/" + id);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Schedule Resource ───────────────────────────────────────────
|
||||
|
||||
class ScheduleResource {
|
||||
constructor(client) { this._c = client; }
|
||||
|
||||
async list() {
|
||||
return this._c._request("GET", "/api/schedules");
|
||||
}
|
||||
|
||||
async create(schedule) {
|
||||
return this._c._request("POST", "/api/schedules", schedule);
|
||||
}
|
||||
|
||||
async update(id, schedule) {
|
||||
return this._c._request("PUT", "/api/schedules/" + id, schedule);
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
return this._c._request("DELETE", "/api/schedules/" + id);
|
||||
}
|
||||
|
||||
async run(id) {
|
||||
return this._c._request("POST", "/api/schedules/" + id + "/run");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Exports ─────────────────────────────────────────────────────
|
||||
|
||||
module.exports = { OpenFang: OpenFang, OpenFangError: OpenFangError };
|
||||
17
sdk/javascript/package.json
Normal file
17
sdk/javascript/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@openfang/sdk",
|
||||
"version": "0.1.0",
|
||||
"description": "Official JavaScript/TypeScript client for the OpenFang Agent OS REST API",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"keywords": ["openfang", "agent", "ai", "llm", "sdk"],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/openfang/openfang"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": ["index.js", "index.d.ts"]
|
||||
}
|
||||
35
sdk/python/examples/client_basic.py
Normal file
35
sdk/python/examples/client_basic.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic example — create an agent and chat with it via the REST API.
|
||||
|
||||
Usage:
|
||||
python client_basic.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from openfang_client import OpenFang
|
||||
|
||||
client = OpenFang("http://localhost:3000")
|
||||
|
||||
# Check server health
|
||||
health = client.health()
|
||||
print("Server:", health)
|
||||
|
||||
# List existing agents
|
||||
agents = client.agents.list()
|
||||
print(f"Agents: {len(agents)}")
|
||||
|
||||
# Create a new agent from the "assistant" template
|
||||
agent = client.agents.create(template="assistant")
|
||||
print(f"Created agent: {agent['id']}")
|
||||
|
||||
# Send a message and get the full response
|
||||
reply = client.agents.message(agent["id"], "What can you help me with?")
|
||||
print(f"Reply: {reply}")
|
||||
|
||||
# Clean up
|
||||
client.agents.delete(agent["id"])
|
||||
print("Agent deleted.")
|
||||
33
sdk/python/examples/client_streaming.py
Normal file
33
sdk/python/examples/client_streaming.py
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Streaming example — stream agent responses token by token.
|
||||
|
||||
Usage:
|
||||
python client_streaming.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from openfang_client import OpenFang
|
||||
|
||||
client = OpenFang("http://localhost:3000")
|
||||
|
||||
# Create an agent
|
||||
agent = client.agents.create(template="assistant")
|
||||
print(f"Agent: {agent['id']}")
|
||||
|
||||
# Stream the response
|
||||
print("\n--- Streaming response ---")
|
||||
for event in client.agents.stream(agent["id"], "Tell me a short story about a robot."):
|
||||
event_type = event.get("type", "")
|
||||
if event_type == "text_delta" and event.get("delta"):
|
||||
print(event["delta"], end="", flush=True)
|
||||
elif event_type == "tool_call":
|
||||
print(f"\n[Tool call: {event.get('tool')}]")
|
||||
elif event_type == "done":
|
||||
print("\n--- Done ---")
|
||||
|
||||
# Clean up
|
||||
client.agents.delete(agent["id"])
|
||||
20
sdk/python/examples/echo_agent.py
Normal file
20
sdk/python/examples/echo_agent.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Example OpenFang agent: echoes back messages with a friendly greeting."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add parent directory to path for openfang_sdk import
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from openfang_sdk import Agent
|
||||
|
||||
agent = Agent()
|
||||
|
||||
|
||||
@agent.on_message
|
||||
def handle(message: str, context: dict) -> str:
|
||||
agent_id = context.get("agent_id", os.environ.get("OPENFANG_AGENT_ID", "unknown"))
|
||||
return f"Hello from Python agent {agent_id}! You said: {message}"
|
||||
|
||||
|
||||
agent.run()
|
||||
367
sdk/python/openfang_client.py
Normal file
367
sdk/python/openfang_client.py
Normal file
@@ -0,0 +1,367 @@
|
||||
"""
|
||||
OpenFang Python Client — REST API client for controlling OpenFang remotely.
|
||||
|
||||
Usage:
|
||||
|
||||
from openfang_client import OpenFang
|
||||
|
||||
client = OpenFang("http://localhost:3000")
|
||||
|
||||
# Create an agent
|
||||
agent = client.agents.create(template="assistant")
|
||||
print(agent["id"])
|
||||
|
||||
# Send a message
|
||||
reply = client.agents.message(agent["id"], "Hello!")
|
||||
print(reply)
|
||||
|
||||
# Stream a response
|
||||
for event in client.agents.stream(agent["id"], "Tell me a joke"):
|
||||
if event.get("type") == "text_delta":
|
||||
print(event["delta"], end="", flush=True)
|
||||
|
||||
Note: This is the REST API *client* library.
|
||||
For writing Python agents that run inside OpenFang, see openfang_sdk.py instead.
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any, Dict, Generator, Optional
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlencode, quote
|
||||
|
||||
|
||||
class OpenFangError(Exception):
|
||||
def __init__(self, message: str, status: int = 0, body: str = ""):
|
||||
super().__init__(message)
|
||||
self.status = status
|
||||
self.body = body
|
||||
|
||||
|
||||
class _Resource:
|
||||
def __init__(self, client: "OpenFang"):
|
||||
self._c = client
|
||||
|
||||
|
||||
class OpenFang:
|
||||
"""OpenFang REST API client. Zero dependencies — uses only stdlib urllib."""
|
||||
|
||||
def __init__(self, base_url: str, headers: Optional[Dict[str, str]] = None):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self._headers = {"Content-Type": "application/json"}
|
||||
if headers:
|
||||
self._headers.update(headers)
|
||||
|
||||
self.agents = _AgentResource(self)
|
||||
self.sessions = _SessionResource(self)
|
||||
self.workflows = _WorkflowResource(self)
|
||||
self.skills = _SkillResource(self)
|
||||
self.channels = _ChannelResource(self)
|
||||
self.tools = _ToolResource(self)
|
||||
self.models = _ModelResource(self)
|
||||
self.providers = _ProviderResource(self)
|
||||
self.memory = _MemoryResource(self)
|
||||
self.triggers = _TriggerResource(self)
|
||||
self.schedules = _ScheduleResource(self)
|
||||
|
||||
def _request(self, method: str, path: str, body: Any = None) -> Any:
|
||||
url = self.base_url + path
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
req = Request(url, data=data, headers=self._headers, method=method)
|
||||
try:
|
||||
with urlopen(req) as resp:
|
||||
ct = resp.headers.get("content-type", "")
|
||||
text = resp.read().decode()
|
||||
if "application/json" in ct:
|
||||
return json.loads(text)
|
||||
return text
|
||||
except HTTPError as e:
|
||||
body_text = e.read().decode() if e.fp else ""
|
||||
raise OpenFangError(f"HTTP {e.code}: {body_text}", e.code, body_text) from e
|
||||
|
||||
def _stream(self, method: str, path: str, body: Any = None) -> Generator[Dict, None, None]:
|
||||
"""SSE streaming. Yields parsed JSON events."""
|
||||
url = self.base_url + path
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
headers = dict(self._headers)
|
||||
headers["Accept"] = "text/event-stream"
|
||||
req = Request(url, data=data, headers=headers, method=method)
|
||||
try:
|
||||
resp = urlopen(req)
|
||||
except HTTPError as e:
|
||||
body_text = e.read().decode() if e.fp else ""
|
||||
raise OpenFangError(f"HTTP {e.code}: {body_text}", e.code, body_text) from e
|
||||
|
||||
buffer = ""
|
||||
while True:
|
||||
chunk = resp.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
buffer += chunk.decode()
|
||||
lines = buffer.split("\n")
|
||||
buffer = lines.pop()
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith("data: "):
|
||||
data_str = line[6:]
|
||||
if data_str == "[DONE]":
|
||||
return
|
||||
try:
|
||||
yield json.loads(data_str)
|
||||
except json.JSONDecodeError:
|
||||
yield {"raw": data_str}
|
||||
resp.close()
|
||||
|
||||
def health(self) -> Any:
|
||||
return self._request("GET", "/api/health")
|
||||
|
||||
def health_detail(self) -> Any:
|
||||
return self._request("GET", "/api/health/detail")
|
||||
|
||||
def status(self) -> Any:
|
||||
return self._request("GET", "/api/status")
|
||||
|
||||
def version(self) -> Any:
|
||||
return self._request("GET", "/api/version")
|
||||
|
||||
def metrics(self) -> str:
|
||||
return self._request("GET", "/api/metrics")
|
||||
|
||||
def usage(self) -> Any:
|
||||
return self._request("GET", "/api/usage")
|
||||
|
||||
def config(self) -> Any:
|
||||
return self._request("GET", "/api/config")
|
||||
|
||||
|
||||
# ── Agent Resource ──────────────────────────────────────────────
|
||||
|
||||
class _AgentResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/agents")
|
||||
|
||||
def get(self, agent_id: str):
|
||||
return self._c._request("GET", f"/api/agents/{agent_id}")
|
||||
|
||||
def create(self, **kwargs):
|
||||
return self._c._request("POST", "/api/agents", kwargs)
|
||||
|
||||
def delete(self, agent_id: str):
|
||||
return self._c._request("DELETE", f"/api/agents/{agent_id}")
|
||||
|
||||
def stop(self, agent_id: str):
|
||||
return self._c._request("POST", f"/api/agents/{agent_id}/stop")
|
||||
|
||||
def clone(self, agent_id: str):
|
||||
return self._c._request("POST", f"/api/agents/{agent_id}/clone")
|
||||
|
||||
def update(self, agent_id: str, **data):
|
||||
return self._c._request("PUT", f"/api/agents/{agent_id}/update", data)
|
||||
|
||||
def set_mode(self, agent_id: str, mode: str):
|
||||
return self._c._request("PUT", f"/api/agents/{agent_id}/mode", {"mode": mode})
|
||||
|
||||
def set_model(self, agent_id: str, model: str):
|
||||
return self._c._request("PUT", f"/api/agents/{agent_id}/model", {"model": model})
|
||||
|
||||
def message(self, agent_id: str, text: str, **opts):
|
||||
body = {"message": text, **opts}
|
||||
return self._c._request("POST", f"/api/agents/{agent_id}/message", body)
|
||||
|
||||
def stream(self, agent_id: str, text: str, **opts) -> Generator[Dict, None, None]:
|
||||
"""Stream response events. Usage:
|
||||
for event in client.agents.stream(id, "Hello"):
|
||||
if event.get("type") == "text_delta":
|
||||
print(event["delta"], end="")
|
||||
"""
|
||||
body = {"message": text, **opts}
|
||||
return self._c._stream("POST", f"/api/agents/{agent_id}/message/stream", body)
|
||||
|
||||
def session(self, agent_id: str):
|
||||
return self._c._request("GET", f"/api/agents/{agent_id}/session")
|
||||
|
||||
def reset_session(self, agent_id: str):
|
||||
return self._c._request("POST", f"/api/agents/{agent_id}/session/reset")
|
||||
|
||||
def compact_session(self, agent_id: str):
|
||||
return self._c._request("POST", f"/api/agents/{agent_id}/session/compact")
|
||||
|
||||
def list_sessions(self, agent_id: str):
|
||||
return self._c._request("GET", f"/api/agents/{agent_id}/sessions")
|
||||
|
||||
def create_session(self, agent_id: str, label: Optional[str] = None):
|
||||
return self._c._request("POST", f"/api/agents/{agent_id}/sessions", {"label": label})
|
||||
|
||||
def switch_session(self, agent_id: str, session_id: str):
|
||||
return self._c._request("POST", f"/api/agents/{agent_id}/sessions/{session_id}/switch")
|
||||
|
||||
def get_skills(self, agent_id: str):
|
||||
return self._c._request("GET", f"/api/agents/{agent_id}/skills")
|
||||
|
||||
def set_skills(self, agent_id: str, skills):
|
||||
return self._c._request("PUT", f"/api/agents/{agent_id}/skills", skills)
|
||||
|
||||
def set_identity(self, agent_id: str, **identity):
|
||||
return self._c._request("PATCH", f"/api/agents/{agent_id}/identity", identity)
|
||||
|
||||
def patch_config(self, agent_id: str, **config):
|
||||
return self._c._request("PATCH", f"/api/agents/{agent_id}/config", config)
|
||||
|
||||
|
||||
# ── Session Resource ────────────────────────────────────────────
|
||||
|
||||
class _SessionResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/sessions")
|
||||
|
||||
def delete(self, session_id: str):
|
||||
return self._c._request("DELETE", f"/api/sessions/{session_id}")
|
||||
|
||||
def set_label(self, session_id: str, label: str):
|
||||
return self._c._request("PUT", f"/api/sessions/{session_id}/label", {"label": label})
|
||||
|
||||
|
||||
# ── Workflow Resource ───────────────────────────────────────────
|
||||
|
||||
class _WorkflowResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/workflows")
|
||||
|
||||
def create(self, **workflow):
|
||||
return self._c._request("POST", "/api/workflows", workflow)
|
||||
|
||||
def run(self, workflow_id: str, input_data=None):
|
||||
return self._c._request("POST", f"/api/workflows/{workflow_id}/run", input_data)
|
||||
|
||||
def runs(self, workflow_id: str):
|
||||
return self._c._request("GET", f"/api/workflows/{workflow_id}/runs")
|
||||
|
||||
|
||||
# ── Skill Resource ──────────────────────────────────────────────
|
||||
|
||||
class _SkillResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/skills")
|
||||
|
||||
def install(self, **skill):
|
||||
return self._c._request("POST", "/api/skills/install", skill)
|
||||
|
||||
def uninstall(self, **skill):
|
||||
return self._c._request("POST", "/api/skills/uninstall", skill)
|
||||
|
||||
def search(self, query: str):
|
||||
return self._c._request("GET", f"/api/marketplace/search?q={quote(query)}")
|
||||
|
||||
|
||||
# ── Channel Resource ────────────────────────────────────────────
|
||||
|
||||
class _ChannelResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/channels")
|
||||
|
||||
def configure(self, name: str, **config):
|
||||
return self._c._request("POST", f"/api/channels/{name}/configure", config)
|
||||
|
||||
def remove(self, name: str):
|
||||
return self._c._request("DELETE", f"/api/channels/{name}/configure")
|
||||
|
||||
def test(self, name: str):
|
||||
return self._c._request("POST", f"/api/channels/{name}/test")
|
||||
|
||||
|
||||
# ── Tool Resource ───────────────────────────────────────────────
|
||||
|
||||
class _ToolResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/tools")
|
||||
|
||||
|
||||
# ── Model Resource ──────────────────────────────────────────────
|
||||
|
||||
class _ModelResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/models")
|
||||
|
||||
def get(self, model_id: str):
|
||||
return self._c._request("GET", f"/api/models/{model_id}")
|
||||
|
||||
def aliases(self):
|
||||
return self._c._request("GET", "/api/models/aliases")
|
||||
|
||||
|
||||
# ── Provider Resource ───────────────────────────────────────────
|
||||
|
||||
class _ProviderResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/providers")
|
||||
|
||||
def set_key(self, name: str, key: str):
|
||||
return self._c._request("POST", f"/api/providers/{name}/key", {"key": key})
|
||||
|
||||
def delete_key(self, name: str):
|
||||
return self._c._request("DELETE", f"/api/providers/{name}/key")
|
||||
|
||||
def test(self, name: str):
|
||||
return self._c._request("POST", f"/api/providers/{name}/test")
|
||||
|
||||
|
||||
# ── Memory Resource ─────────────────────────────────────────────
|
||||
|
||||
class _MemoryResource(_Resource):
|
||||
|
||||
def get_all(self, agent_id: str):
|
||||
return self._c._request("GET", f"/api/memory/agents/{agent_id}/kv")
|
||||
|
||||
def get(self, agent_id: str, key: str):
|
||||
return self._c._request("GET", f"/api/memory/agents/{agent_id}/kv/{key}")
|
||||
|
||||
def set(self, agent_id: str, key: str, value):
|
||||
return self._c._request("PUT", f"/api/memory/agents/{agent_id}/kv/{key}", {"value": value})
|
||||
|
||||
def delete(self, agent_id: str, key: str):
|
||||
return self._c._request("DELETE", f"/api/memory/agents/{agent_id}/kv/{key}")
|
||||
|
||||
|
||||
# ── Trigger Resource ────────────────────────────────────────────
|
||||
|
||||
class _TriggerResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/triggers")
|
||||
|
||||
def create(self, **trigger):
|
||||
return self._c._request("POST", "/api/triggers", trigger)
|
||||
|
||||
def update(self, trigger_id: str, **trigger):
|
||||
return self._c._request("PUT", f"/api/triggers/{trigger_id}", trigger)
|
||||
|
||||
def delete(self, trigger_id: str):
|
||||
return self._c._request("DELETE", f"/api/triggers/{trigger_id}")
|
||||
|
||||
|
||||
# ── Schedule Resource ───────────────────────────────────────────
|
||||
|
||||
class _ScheduleResource(_Resource):
|
||||
|
||||
def list(self):
|
||||
return self._c._request("GET", "/api/schedules")
|
||||
|
||||
def create(self, **schedule):
|
||||
return self._c._request("POST", "/api/schedules", schedule)
|
||||
|
||||
def update(self, schedule_id: str, **schedule):
|
||||
return self._c._request("PUT", f"/api/schedules/{schedule_id}", schedule)
|
||||
|
||||
def delete(self, schedule_id: str):
|
||||
return self._c._request("DELETE", f"/api/schedules/{schedule_id}")
|
||||
|
||||
def run(self, schedule_id: str):
|
||||
return self._c._request("POST", f"/api/schedules/{schedule_id}/run")
|
||||
147
sdk/python/openfang_sdk.py
Normal file
147
sdk/python/openfang_sdk.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
OpenFang Python SDK — helper library for writing Python agents.
|
||||
|
||||
Usage:
|
||||
|
||||
from openfang_sdk import Agent
|
||||
|
||||
agent = Agent()
|
||||
|
||||
@agent.on_message
|
||||
def handle(message: str, context: dict) -> str:
|
||||
return f"You said: {message}"
|
||||
|
||||
agent.run()
|
||||
|
||||
Or for simple scripts without the decorator pattern:
|
||||
|
||||
from openfang_sdk import read_input, respond
|
||||
|
||||
data = read_input()
|
||||
result = f"Echo: {data['message']}"
|
||||
respond(result)
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Callable, Optional, Dict, Any
|
||||
|
||||
|
||||
def read_input() -> Dict[str, Any]:
|
||||
"""Read the input JSON from stdin (sent by the OpenFang kernel)."""
|
||||
line = sys.stdin.readline().strip()
|
||||
if not line:
|
||||
# Fallback: check environment variables
|
||||
agent_id = os.environ.get("OPENFANG_AGENT_ID", "")
|
||||
message = os.environ.get("OPENFANG_MESSAGE", "")
|
||||
return {
|
||||
"type": "message",
|
||||
"agent_id": agent_id,
|
||||
"message": message,
|
||||
"context": {},
|
||||
}
|
||||
return json.loads(line)
|
||||
|
||||
|
||||
def respond(text: str, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||
"""Send a response back to the OpenFang kernel via stdout."""
|
||||
response = {"type": "response", "text": text}
|
||||
if metadata:
|
||||
response["metadata"] = metadata
|
||||
print(json.dumps(response), flush=True)
|
||||
|
||||
|
||||
def log(message: str, level: str = "info") -> None:
|
||||
"""Log a message to stderr (visible in OpenFang daemon logs)."""
|
||||
print(f"[{level.upper()}] {message}", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
class Agent:
|
||||
"""Decorator-based Python agent framework.
|
||||
|
||||
Example:
|
||||
|
||||
agent = Agent()
|
||||
|
||||
@agent.on_message
|
||||
def handle(message: str, context: dict) -> str:
|
||||
return f"Hello! You said: {message}"
|
||||
|
||||
agent.run()
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._handler: Optional[Callable] = None
|
||||
self._setup: Optional[Callable] = None
|
||||
self._teardown: Optional[Callable] = None
|
||||
|
||||
def on_message(self, func: Callable) -> Callable:
|
||||
"""Register a message handler function.
|
||||
|
||||
The function should accept (message: str, context: dict) and return str.
|
||||
"""
|
||||
self._handler = func
|
||||
return func
|
||||
|
||||
def on_setup(self, func: Callable) -> Callable:
|
||||
"""Register a setup function called once before message handling."""
|
||||
self._setup = func
|
||||
return func
|
||||
|
||||
def on_teardown(self, func: Callable) -> Callable:
|
||||
"""Register a teardown function called once after message handling."""
|
||||
self._teardown = func
|
||||
return func
|
||||
|
||||
def run(self) -> None:
|
||||
"""Run the agent, reading input and producing output."""
|
||||
if self._handler is None:
|
||||
log("No message handler registered", "error")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
if self._setup:
|
||||
self._setup()
|
||||
|
||||
data = read_input()
|
||||
message = data.get("message", "")
|
||||
context = data.get("context", {})
|
||||
|
||||
result = self._handler(message, context)
|
||||
|
||||
if isinstance(result, str):
|
||||
respond(result)
|
||||
elif isinstance(result, dict):
|
||||
respond(result.get("text", str(result)), result.get("metadata"))
|
||||
else:
|
||||
respond(str(result))
|
||||
|
||||
except Exception as e:
|
||||
log(f"Agent error: {e}", "error")
|
||||
respond(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if self._teardown:
|
||||
try:
|
||||
self._teardown()
|
||||
except Exception as e:
|
||||
log(f"Teardown error: {e}", "error")
|
||||
|
||||
|
||||
# Convenience: if this file is run directly, show usage
|
||||
if __name__ == "__main__":
|
||||
print("OpenFang Python SDK")
|
||||
print("====================")
|
||||
print()
|
||||
print("Import this module in your agent scripts:")
|
||||
print()
|
||||
print(" from openfang_sdk import Agent")
|
||||
print()
|
||||
print(" agent = Agent()")
|
||||
print()
|
||||
print(" @agent.on_message")
|
||||
print(" def handle(message, context):")
|
||||
print(" return f'You said: {message}'")
|
||||
print()
|
||||
print(" agent.run()")
|
||||
14
sdk/python/setup.py
Normal file
14
sdk/python/setup.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="openfang",
|
||||
version="0.1.0",
|
||||
description="Official Python client for the OpenFang Agent OS REST API",
|
||||
py_modules=["openfang_sdk", "openfang_client"],
|
||||
python_requires=">=3.8",
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
)
|
||||
Reference in New Issue
Block a user