Skip to content

Commit 281cfbd

Browse files
committed
docs: fix HIGH_LEVEL_API.md — use provider-first style, add local providers
- Replace all legacy model: 'provider:model' colon syntax with the recommended provider + model separate fields pattern - Add provider type column (Cloud vs Local) to defaults table - Add env var column so users know what to set - Document local providers: ollama (text + image), stable-diffusion-local - Add local image generation section with Ollama and SD WebUI examples - Add Anthropic→OpenRouter automatic fallback note - Fix providerOptions examples (stability, replicate) to use provider-first - Fix custom provider example to use provider-first
1 parent 007005a commit 281cfbd

12 files changed

Lines changed: 367 additions & 30 deletions

docs/HIGH_LEVEL_API.md

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,58 @@ If you also want durable helper-level accounting, set `usageLedger.path`, set `u
2121
| `agent()` | Lightweight multi-turn sessions with in-memory history | Does not replace the full AgentOS runtime |
2222
| `AgentOS` | Personas, extensions, workflows, multi-agent orchestration, guardrails, HITL, runtime lifecycle | More setup, more control |
2323

24-
## Provider Defaults
24+
## Provider Resolution
25+
26+
### Calling Styles
27+
28+
AgentOS supports three styles for specifying provider and model. **Provider-first is recommended:**
29+
30+
```ts
31+
// 1. Provider-first (recommended) — AgentOS picks the best default model
32+
await generateText({ provider: 'openai', prompt: '...' });
33+
34+
// 2. Provider + explicit model — full control
35+
await generateText({ provider: 'anthropic', model: 'claude-sonnet-4-20250514', prompt: '...' });
36+
37+
// 3. Legacy colon format — backwards compatible, still works
38+
await generateText({ model: 'openai:gpt-4o', prompt: '...' });
39+
```
40+
41+
### Provider Defaults
2542

2643
When you supply `provider` without an explicit `model`, AgentOS resolves the default model
2744
for the requested task automatically:
2845

29-
| Provider | Text default | Image default | Embedding default |
30-
|----------|-------------|---------------|-------------------|
31-
| `openai` | `gpt-4o` | `gpt-image-1` | `text-embedding-3-small` |
32-
| `anthropic` | `claude-sonnet-4-20250514` |||
33-
| `ollama` | `llama3.2` | `stable-diffusion` | `nomic-embed-text` |
34-
| `openrouter` | `openai/gpt-4o` |||
35-
| `gemini` | `gemini-2.5-flash` |||
36-
| `stability` || `stable-diffusion-xl-1024-v1-0` ||
37-
| `replicate` || `black-forest-labs/flux-1.1-pro` ||
46+
| Provider | Type | Text default | Image default | Embedding default | Env var |
47+
|----------|------|-------------|---------------|-------------------|---------|
48+
| `openai` | Cloud | `gpt-4o` | `gpt-image-1` | `text-embedding-3-small` | `OPENAI_API_KEY` |
49+
| `anthropic` | Cloud | `claude-sonnet-4-20250514` ||| `ANTHROPIC_API_KEY` |
50+
| `gemini` | Cloud | `gemini-2.5-flash` ||| `GEMINI_API_KEY` |
51+
| `openrouter` | Cloud | `openai/gpt-4o` ||| `OPENROUTER_API_KEY` |
52+
| `stability` | Cloud || `stable-diffusion-xl-1024-v1-0` || `STABILITY_API_KEY` |
53+
| `replicate` | Cloud || `black-forest-labs/flux-1.1-pro` || `REPLICATE_API_TOKEN` |
54+
| `ollama` | Local | `llama3.2` | `stable-diffusion` | `nomic-embed-text` | `OLLAMA_BASE_URL` |
55+
| `stable-diffusion-local` | Local || `v1-5-pruned-emaonly` || `STABLE_DIFFUSION_LOCAL_BASE_URL` |
3856

3957
When neither `provider` nor `model` is given, the first set API key env var is used
4058
(`OPENAI_API_KEY``ANTHROPIC_API_KEY``OPENROUTER_API_KEY``GEMINI_API_KEY``OLLAMA_BASE_URL`).
4159

42-
The legacy `model: 'provider:model'` format is fully supported alongside the new style.
60+
### Local Providers
61+
62+
Local providers don't require API keys — just a `baseUrl` (or the corresponding env var):
63+
64+
```ts
65+
// Ollama — runs any GGUF model locally
66+
await generateText({
67+
provider: 'ollama',
68+
model: 'llama3.2',
69+
prompt: 'Explain quantum entanglement simply.',
70+
baseUrl: 'http://localhost:11434', // or set OLLAMA_BASE_URL
71+
});
72+
73+
// Anthropic fallback: if ANTHROPIC_API_KEY is unset but OPENROUTER_API_KEY is set,
74+
// AgentOS automatically routes anthropic requests through OpenRouter.
75+
```
4376

4477
## `generateText()`
4578

@@ -110,21 +143,28 @@ console.log(result.provider);
110143
console.log(result.images[0]?.mimeType);
111144
```
112145

113-
Built-in image providers:
146+
### Built-in Image Providers
114147

115-
- `openai`
116-
- `openrouter`
117-
- `stability`
118-
- `replicate`
148+
| Provider | Type | Default model | API key env var |
149+
|---|---|---|---|
150+
| `openai` | Cloud API | `gpt-image-1` | `OPENAI_API_KEY` |
151+
| `stability` | Cloud API | `stable-diffusion-xl-1024-v1-0` | `STABILITY_API_KEY` |
152+
| `replicate` | Cloud API | `black-forest-labs/flux-1.1-pro` | `REPLICATE_API_TOKEN` |
153+
| `openrouter` | Cloud API || `OPENROUTER_API_KEY` |
154+
| `ollama` | Local | `stable-diffusion` | None (uses `baseUrl`) |
155+
| `stable-diffusion-local` | Local | `v1-5-pruned-emaonly` | None (uses `baseUrl`) |
156+
157+
### Provider-Specific Options
119158

120159
Use the common options for the simple path, then drop down to namespaced
121-
`providerOptions` when you need provider-specific controls:
160+
`providerOptions` when you need provider-native controls:
122161

123162
```ts
124163
import { generateImage } from '@framers/agentos';
125164

126165
const poster = await generateImage({
127-
model: 'stability:stable-image-core',
166+
provider: 'stability',
167+
model: 'stable-image-core',
128168
prompt: 'An art deco travel poster for a moon colony',
129169
negativePrompt: 'text, watermark',
130170
providerOptions: {
@@ -143,7 +183,8 @@ Replicate and OpenRouter work the same way:
143183

144184
```ts
145185
const replicateResult = await generateImage({
146-
model: 'replicate:black-forest-labs/flux-schnell',
186+
provider: 'replicate',
187+
model: 'black-forest-labs/flux-schnell',
147188
prompt: 'A product photo of a titanium watch on black stone',
148189
aspectRatio: '16:9',
149190
providerOptions: {
@@ -157,8 +198,31 @@ const replicateResult = await generateImage({
157198
});
158199
```
159200

160-
If you need a custom backend entirely, register a provider factory and still use
161-
the same `generateImage()` surface:
201+
### Local Image Generation
202+
203+
Run Stable Diffusion locally without any API key:
204+
205+
```ts
206+
// Via Ollama (if your Ollama install has a stable-diffusion model)
207+
const local = await generateImage({
208+
provider: 'ollama',
209+
model: 'stable-diffusion',
210+
prompt: 'A watercolor landscape of rolling hills',
211+
baseUrl: 'http://localhost:11434', // or set OLLAMA_BASE_URL
212+
});
213+
214+
// Via local Stable Diffusion WebUI (Automatic1111 / ComfyUI)
215+
const sdLocal = await generateImage({
216+
provider: 'stable-diffusion-local',
217+
model: 'v1-5-pruned-emaonly',
218+
prompt: 'A brutalist house in fog',
219+
baseUrl: 'http://localhost:7860', // or set STABLE_DIFFUSION_LOCAL_BASE_URL
220+
});
221+
```
222+
223+
### Custom Image Provider
224+
225+
Register a provider factory for backends not covered by the built-ins:
162226

163227
```ts
164228
import {
@@ -167,7 +231,7 @@ import {
167231
type IImageProvider,
168232
} from '@framers/agentos';
169233

170-
class CustomImageProvider implements IImageProvider {
234+
class ComfyUIProvider implements IImageProvider {
171235
providerId = 'comfyui';
172236
isInitialized = false;
173237
defaultModelId = 'sdxl';
@@ -187,10 +251,11 @@ class CustomImageProvider implements IImageProvider {
187251
}
188252
}
189253

190-
registerImageProviderFactory('comfyui', () => new CustomImageProvider());
254+
registerImageProviderFactory('comfyui', () => new ComfyUIProvider());
191255

192256
await generateImage({
193-
model: 'comfyui:sdxl',
257+
provider: 'comfyui',
258+
model: 'sdxl',
194259
prompt: 'A brutalist house in fog',
195260
});
196261
```

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"name": "@framers/agentos",
33
"version": "0.1.97",
4+
45
"description": "Modular AgentOS orchestration library",
56
"type": "module",
67
"main": "dist/index.js",

src/discovery/CapabilityDiscoveryEngine.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,32 @@ export class CapabilityDiscoveryEngine implements ICapabilityDiscoveryEngine {
268268
return descriptors.length;
269269
}
270270

271+
/**
272+
* Remove emergent tools from the discovery index.
273+
*
274+
* This is used when a promoted/shared tool is deactivated or revoked and
275+
* should no longer be returned by semantic capability discovery.
276+
*/
277+
async removeEmergentTools(tools: EmergentTool[]): Promise<number> {
278+
const eligible = tools.filter((t) => t.tier !== 'session');
279+
280+
if (eligible.length === 0) return 0;
281+
282+
let removed = 0;
283+
for (const tool of eligible) {
284+
await this.index.removeCapability(`emergent-tool:${tool.name}`);
285+
removed += 1;
286+
}
287+
288+
const allCapabilities = this.index.getAllCapabilities();
289+
await this.graph.buildGraph(allCapabilities);
290+
291+
this.indexVersion++;
292+
this.assembler.invalidateCache();
293+
294+
return removed;
295+
}
296+
271297
// ============================================================================
272298
// ACCESSORS
273299
// ============================================================================

src/discovery/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,4 +444,7 @@ export interface ICapabilityDiscoveryEngine {
444444

445445
/** Index promoted emergent tools into discovery. */
446446
indexEmergentTools?(tools: EmergentTool[]): Promise<number>;
447+
448+
/** Remove emergent tools from discovery when they are deactivated or revoked. */
449+
removeEmergentTools?(tools: EmergentTool[]): Promise<number>;
447450
}

src/emergent/EmergentCapabilityEngine.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ export interface EmergentCapabilityEngineDeps {
6464

6565
/** Optional callback used when a tool is promoted to a persisted tier. */
6666
onToolPromoted?: (tool: EmergentTool) => Promise<void>;
67+
68+
/** Optional callback used when a tool is removed from the live runtime. */
69+
onToolRemoved?: (tool: EmergentTool) => Promise<void>;
6770
}
6871

6972
// ============================================================================
@@ -120,6 +123,7 @@ export class EmergentCapabilityEngine {
120123
private readonly registry: EmergentToolRegistry;
121124
private readonly onToolForged?: (tool: EmergentTool, executable: ITool) => Promise<void>;
122125
private readonly onToolPromoted?: (tool: EmergentTool) => Promise<void>;
126+
private readonly onToolRemoved?: (tool: EmergentTool) => Promise<void>;
123127

124128
/** Internal index for fast session/agent → tool lookups. */
125129
private readonly index: ToolIndex = {
@@ -140,6 +144,7 @@ export class EmergentCapabilityEngine {
140144
this.registry = deps.registry;
141145
this.onToolForged = deps.onToolForged;
142146
this.onToolPromoted = deps.onToolPromoted;
147+
this.onToolRemoved = deps.onToolRemoved;
143148
}
144149

145150
// --------------------------------------------------------------------------
@@ -225,6 +230,14 @@ export class EmergentCapabilityEngine {
225230
}
226231
} else {
227232
// ---- SANDBOX MODE ----
233+
if (!this.config.allowSandboxTools) {
234+
return {
235+
success: false,
236+
error:
237+
'Sandboxed emergent tools are disabled. Enable allowSandboxTools to permit code-forged tools.',
238+
};
239+
}
240+
228241
source = request.implementation.code;
229242

230243
// Step 2a: Static code validation before any execution.
@@ -465,6 +478,43 @@ export class EmergentCapabilityEngine {
465478
return removedTools;
466479
}
467480

481+
/**
482+
* Hydrate a persisted tool back into a live runtime and make it executable.
483+
*
484+
* This is used by backend/admin control planes to sync shared tools from
485+
* durable storage into a running ToolOrchestrator after promotion or restart.
486+
*/
487+
async syncPersistedTool(tool: EmergentTool): Promise<void> {
488+
this.registry.upsert(tool);
489+
this.indexTool(tool.id, tool.createdBy, this.extractSessionId(tool.source) ?? `persisted:${tool.id}`);
490+
491+
const isActive = (tool as EmergentTool & { isActive?: boolean }).isActive ?? true;
492+
if (!isActive) {
493+
return;
494+
}
495+
496+
if (this.onToolForged) {
497+
await this.onToolForged(tool, this.createExecutableTool(tool));
498+
}
499+
}
500+
501+
/**
502+
* Remove a previously synced tool from the live runtime and registry.
503+
*/
504+
async removeTool(toolId: string): Promise<EmergentTool | undefined> {
505+
const tool = this.registry.get(toolId);
506+
if (!tool) {
507+
return undefined;
508+
}
509+
510+
this.registry.remove(toolId);
511+
this.removeIndexedToolEverywhere(toolId);
512+
if (this.onToolRemoved) {
513+
await this.onToolRemoved(tool);
514+
}
515+
return tool;
516+
}
517+
468518
/**
469519
* Create an executable ITool wrapper for a forged emergent tool.
470520
*
@@ -575,6 +625,20 @@ export class EmergentCapabilityEngine {
575625
this.index.byAgent.get(agentId)?.delete(toolId);
576626
}
577627

628+
private removeIndexedToolEverywhere(toolId: string): void {
629+
for (const toolIds of this.index.bySession.values()) {
630+
toolIds.delete(toolId);
631+
}
632+
for (const toolIds of this.index.byAgent.values()) {
633+
toolIds.delete(toolId);
634+
}
635+
}
636+
637+
private extractSessionId(source: string): string | null {
638+
const match = /session\s+([A-Za-z0-9._:-]+)/i.exec(source);
639+
return match?.[1] ?? null;
640+
}
641+
578642
private buildSandboxExecutable(tool: EmergentTool): ITool<Record<string, unknown>, unknown> {
579643
return {
580644
id: `sandboxed:${tool.id}`,

0 commit comments

Comments
 (0)