@inneranimalmedia/agentsam-sdk 2.6.3 → 2.6.4
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/AGENTSAM.md +6 -0
- package/README.md +16 -1
- package/bin/agentsam +16 -1
- package/docs/BRAND_INTELLIGENCE.md +1 -1
- package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
- package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
- package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
- package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
- package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +28 -0
- package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
- package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
- package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
- package/docs/architecture/SAM_KERNEL.md +181 -0
- package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
- package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
- package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
- package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
- package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
- package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
- package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
- package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
- package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
- package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
- package/migrations/d1/0013_identity_oauth_states.sql +15 -0
- package/migrations/d1/0014_auth_event_log.sql +20 -0
- package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
- package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
- package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
- package/package.json +19 -4
- package/packages/agentsam-contracts/src/errors.ts +16 -0
- package/packages/agentsam-errors/src/envelope.js +53 -0
- package/packages/agentsam-errors/src/index.js +1 -0
- package/packages/agentsam-errors/src/recovery.js +301 -0
- package/packages/agentsam-knowledge/src/providers/index.js +18 -6
- package/packages/connectors/cloudflare/src/routes.js +9 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +11 -2
- package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
- package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +31 -0
- package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
- package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
- package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/adapters/cloudflare-d1/index.js +122 -22
- package/packages/identity/src/adapters/sqlite/index.js +319 -0
- package/packages/identity/src/app/verify-app.js +95 -0
- package/packages/identity/src/contracts/identity-store.js +115 -0
- package/packages/identity/src/contracts/route-ids.js +30 -0
- package/packages/identity/src/contracts/route-projection.js +218 -0
- package/packages/identity/src/contracts/routes.js +11 -0
- package/packages/identity/src/core/browser-paths.js +4 -5
- package/packages/identity/src/core/constants.js +13 -8
- package/packages/identity/src/core/session-policy.js +32 -0
- package/packages/identity/src/frontend/auth-portal/pages/login.html +10 -10
- package/packages/identity/src/frontend/auth-portal/pages/reset.html +3 -3
- package/packages/identity/src/frontend/auth-portal/pages/signup.html +3 -3
- package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
- package/packages/identity/src/index.js +25 -0
- package/packages/identity/src/oauth/README.md +10 -4
- package/packages/identity/src/oauth/credentials.js +20 -13
- package/packages/identity/src/oauth/finalize-inbound.js +1 -1
- package/packages/identity/src/oauth/iam-platform.js +8 -7
- package/packages/identity/src/oauth/redirect-paths.js +27 -36
- package/packages/identity/src/server/identity-service.js +30 -13
- package/packages/identity/src/server/mount-policy.js +30 -0
- package/packages/identity/src/server/post-auth.js +79 -0
- package/packages/identity/src/server/worker-router.js +104 -72
- package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
- package/packages/identity/tests/iam-provider.test.mjs +1 -1
- package/packages/identity/tests/identity-service.test.mjs +36 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +3 -1
- package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
- package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
- package/packages/theme-church-site/package.json +2 -1
- package/packages/theme-church-site/src/index.js +1 -0
- package/packages/theme-companions-site/package.json +2 -1
- package/packages/theme-companions-site/src/index.js +1 -0
- package/packages/theme-floors-site/package.json +2 -1
- package/packages/theme-floors-site/src/index.js +1 -0
- package/packages/theme-fuelnfree-site/package.json +2 -1
- package/packages/theme-fuelnfree-site/src/index.js +1 -0
- package/packages/theme-handyman-site/package.json +2 -1
- package/packages/theme-handyman-site/src/index.js +1 -0
- package/packages/theme-insurance-site/package.json +2 -1
- package/packages/theme-insurance-site/src/index.js +1 -0
- package/packages/theme-shinshu-site/package.json +2 -1
- package/packages/theme-shinshu-site/src/index.js +1 -0
- package/protocol/apps/agentsam.app.v1.schema.json +51 -0
- package/protocol/brand/brandpack.v1.schema.json +43 -0
- package/protocol/credentials/issue.v1.schema.json +38 -0
- package/protocol/database/connection.v1.schema.json +41 -0
- package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
- package/protocol/errors/error-envelope.schema.json +135 -1
- package/protocol/errors/recovery.v1.schema.json +50 -0
- package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
- package/protocol/sam/activity.v1.schema.json +48 -0
- package/protocol/sam/answer.v1.schema.json +35 -0
- package/protocol/sam/calibration.v1.schema.json +21 -0
- package/protocol/sam/decision-receipt.v1.schema.json +29 -0
- package/protocol/sam/evaluation.v1.schema.json +19 -0
- package/protocol/sam/operation.schema.json +66 -0
- package/protocol/sam/outcome.v1.schema.json +36 -0
- package/protocol/sam/question.v1.schema.json +26 -0
- package/protocol/sam/registry.seed.json +153 -0
- package/protocol/sam/result.schema.json +53 -0
- package/protocol/sam/state.v1.schema.json +23 -0
- package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
- package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
- package/protocol/ui/icon-registry.mjs +226 -0
- package/protocol/ui/icon.v1.schema.json +51 -0
- package/skills/README.md +22 -9
- package/skills/agentsam-codebaseindex/SKILL.md +225 -0
- package/skills/catalog.json +14 -0
- package/src/cli/command-catalog.js +130 -0
- package/src/cli/dispatch.js +48 -0
- package/src/cli.js +43 -1
- package/src/commands/api-key.js +244 -0
- package/src/commands/app.js +60 -28
- package/src/commands/brand.js +17 -19
- package/src/commands/codebaseindex.js +688 -0
- package/src/commands/env.js +152 -25
- package/src/commands/go.js +366 -53
- package/src/commands/interaction-clack.js +115 -0
- package/src/commands/models.js +1 -1
- package/src/commands/providers.js +62 -14
- package/src/commands/shell.js +43 -2
- package/src/commands/skill.js +248 -0
- package/src/commands/skills.js +1 -1
- package/src/commands/start-local.js +4 -0
- package/src/commands/whoami.js +90 -18
- package/src/go/build.js +229 -39
- package/src/go/cloudflare.js +506 -105
- package/src/go/container.js +120 -0
- package/src/go/contract.js +9 -4
- package/src/go/discover.js +149 -33
- package/src/go/index.js +15 -3
- package/src/go/native-probe-runner.mjs +119 -0
- package/src/go/{registry.js → official-registry.js} +64 -7
- package/src/go/official-release.js +10 -0
- package/src/go/receipts.js +77 -10
- package/src/go/verify.js +9 -2
- package/src/index.js +27 -0
- package/src/indexing/ingest/discover-models.js +298 -0
- package/src/indexing/ingest/inventory.js +243 -0
- package/src/indexing/ingest/job-graph.js +181 -0
- package/src/indexing/ingest/materials.js +210 -0
- package/src/lib/provider-credentials.js +63 -21
- package/src/lib/slash-commands.js +1 -0
- package/src/local-fs/capability.js +121 -0
- package/src/local-fs/freshness.js +75 -0
- package/src/local-fs/index.js +385 -0
- package/src/local-fs/paths.js +100 -0
- package/src/local-pty/server.js +295 -19
- package/src/mcp/client.js +2 -2
- package/src/models/ai-access-onboarding.js +112 -0
- package/src/models/discovery.js +10 -2
- package/src/models/inventory-core.js +9 -1
- package/src/sam/activity/index.js +183 -0
- package/src/sam/client.js +252 -0
- package/src/sam/decision/calibration.js +109 -0
- package/src/sam/decision/confidence.js +126 -0
- package/src/sam/decision/evaluate.js +157 -0
- package/src/sam/decision/evaluators/deterministic.js +341 -0
- package/src/sam/decision/evaluators/heuristic.js +61 -0
- package/src/sam/decision/evaluators/select.js +50 -0
- package/src/sam/decision/evaluators/semantic.js +149 -0
- package/src/sam/decision/hierarchical.js +61 -0
- package/src/sam/decision/index.js +53 -0
- package/src/sam/decision/policy.js +86 -0
- package/src/sam/decision/questions.js +120 -0
- package/src/sam/decision/receipt.js +148 -0
- package/src/sam/decision/state.js +117 -0
- package/src/sam/decision/types.js +22 -0
- package/src/sam/decision/validate.js +200 -0
- package/src/sam/define.js +51 -0
- package/src/sam/index.js +65 -0
- package/src/sam/operations/brand-scan.js +62 -0
- package/src/sam/operations/cad-blender-inspect.js +36 -0
- package/src/sam/operations/codebaseindex-ingest.js +49 -0
- package/src/sam/operations/decision-evaluate.js +59 -0
- package/src/sam/operations/planning-astar.js +77 -0
- package/src/sam/operations/planning-goap.js +60 -0
- package/src/sam/operations/repository-inspect.js +72 -0
- package/src/sam/operations/security-scan.js +33 -0
- package/src/sam/operations/terminal-exec.js +29 -0
- package/src/sam/planning/astar.js +311 -0
- package/src/sam/planning/goap.js +177 -0
- package/src/sam/planning/index.js +21 -0
- package/src/sam/planning/state.js +84 -0
- package/src/sam/registry.js +48 -0
- package/src/sam/result.js +77 -0
- package/src/sam/seed.js +44 -0
- package/src/sam/types.js +91 -0
- package/src/skills/catalog.js +64 -0
- package/src/skills/content-resolver.js +124 -0
- package/src/skills/hosted-store.js +37 -0
- package/src/skills/index.js +29 -64
- package/src/skills/interaction.js +102 -0
- package/src/skills/local-store.js +228 -0
- package/src/skills/manifest.js +104 -0
- package/src/skills/metrics.js +31 -0
- package/src/skills/registry.js +184 -0
- package/src/skills/runtime.js +287 -0
- package/src/skills/slash.js +44 -0
- package/src/ui/cli/help.js +94 -101
- package/test/cli/api-key-env-whoami.test.mjs +129 -0
- package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
- package/test/cli/go.test.mjs +62 -5
- package/test/cli/skill-npm-and-env.test.mjs +52 -0
- package/test/cli/wireframes-go-registry.test.mjs +87 -2
- package/test/go/build-source-identity.test.mjs +31 -0
- package/test/go/cloudflare-probe.test.mjs +274 -10
- package/test/go/distribution.test.mjs +28 -0
- package/test/integration/ai-access-onboarding.test.mjs +47 -0
- package/test/integration/cli-help.test.mjs +1 -1
- package/test/integration/cms-site-tenancy-contract.test.mjs +6 -6
- package/test/integration/codebaseindex-ingest.test.mjs +166 -0
- package/test/integration/icon-registry.test.mjs +67 -0
- package/test/integration/ingest-discover-models.test.mjs +30 -0
- package/test/integration/install-script.test.mjs +12 -9
- package/test/integration/local-fs.test.mjs +113 -0
- package/test/integration/provider-env-cli.test.mjs +2 -1
- package/test/integration/sam-activity-recovery.test.mjs +147 -0
- package/test/integration/sam-decision.test.mjs +584 -0
- package/test/integration/sam-kernel.test.mjs +99 -0
- package/test/integration/sam-planning-astar.test.mjs +279 -0
- package/test/integration/skill-runtime.test.mjs +240 -0
- package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
- package/test/models.test.mjs +14 -7
- package/test/shell.test.mjs +4 -4
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path containment for local machine filesystem authority.
|
|
3
|
+
* Rejects NUL, traversal, and symlink escapes outside the approved root.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} root
|
|
10
|
+
* @param {string} relativeOrAbs
|
|
11
|
+
* @returns {{ ok: true, abs: string, rel: string } | { ok: false, error: string, code: string }}
|
|
12
|
+
*/
|
|
13
|
+
export function resolveContainedPath(root, relativeOrAbs) {
|
|
14
|
+
if (typeof root !== 'string' || !root.trim()) {
|
|
15
|
+
return { ok: false, error: 'workspace_root_required', code: 'invalid_root' };
|
|
16
|
+
}
|
|
17
|
+
if (typeof relativeOrAbs !== 'string') {
|
|
18
|
+
return { ok: false, error: 'path_required', code: 'invalid_path' };
|
|
19
|
+
}
|
|
20
|
+
if (relativeOrAbs.includes('\0')) {
|
|
21
|
+
return { ok: false, error: 'nul_in_path', code: 'invalid_path' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let rootReal;
|
|
25
|
+
try {
|
|
26
|
+
rootReal = fs.realpathSync(path.resolve(root));
|
|
27
|
+
} catch (err) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: err instanceof Error ? err.message : String(err),
|
|
31
|
+
code: 'root_unresolvable',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const joined = path.isAbsolute(relativeOrAbs)
|
|
36
|
+
? path.normalize(relativeOrAbs)
|
|
37
|
+
: path.resolve(rootReal, relativeOrAbs);
|
|
38
|
+
|
|
39
|
+
// Reject obvious escapes before realpath (missing parents still checked via prefix).
|
|
40
|
+
const relToRoot = path.relative(rootReal, joined);
|
|
41
|
+
if (relToRoot.startsWith('..') || path.isAbsolute(relToRoot)) {
|
|
42
|
+
return { ok: false, error: 'path_escape', code: 'path_escape' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let absReal = joined;
|
|
46
|
+
try {
|
|
47
|
+
absReal = fs.realpathSync(joined);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
// File may not exist yet (create/write) — ensure parent is inside root.
|
|
50
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {
|
|
51
|
+
const parent = path.dirname(joined);
|
|
52
|
+
let parentReal;
|
|
53
|
+
try {
|
|
54
|
+
parentReal = fs.realpathSync(parent);
|
|
55
|
+
} catch (parentErr) {
|
|
56
|
+
// Walk up until an existing ancestor within root.
|
|
57
|
+
let cursor = parent;
|
|
58
|
+
let found = null;
|
|
59
|
+
while (cursor.startsWith(rootReal)) {
|
|
60
|
+
if (fs.existsSync(cursor)) {
|
|
61
|
+
try {
|
|
62
|
+
found = fs.realpathSync(cursor);
|
|
63
|
+
break;
|
|
64
|
+
} catch {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const next = path.dirname(cursor);
|
|
69
|
+
if (next === cursor) break;
|
|
70
|
+
cursor = next;
|
|
71
|
+
}
|
|
72
|
+
if (!found || (found !== rootReal && !found.startsWith(rootReal + path.sep))) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: parentErr instanceof Error ? parentErr.message : String(parentErr),
|
|
76
|
+
code: 'path_escape',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
parentReal = found;
|
|
80
|
+
}
|
|
81
|
+
if (parentReal !== rootReal && !parentReal.startsWith(rootReal + path.sep)) {
|
|
82
|
+
return { ok: false, error: 'path_escape', code: 'path_escape' };
|
|
83
|
+
}
|
|
84
|
+
absReal = joined;
|
|
85
|
+
} else {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: err instanceof Error ? err.message : String(err),
|
|
89
|
+
code: 'path_unresolvable',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (absReal !== rootReal && !absReal.startsWith(rootReal + path.sep)) {
|
|
95
|
+
return { ok: false, error: 'symlink_escape', code: 'path_escape' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const rel = absReal === rootReal ? '.' : path.relative(rootReal, absReal).split(path.sep).join('/');
|
|
99
|
+
return { ok: true, abs: absReal, rel: rel || '.' };
|
|
100
|
+
}
|
package/src/local-pty/server.js
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent Sam local PTY — localhost WebSocket shell
|
|
3
|
-
* Compatible with iam-pty wire format (raw bytes + JSON resize/slash).
|
|
2
|
+
* Agent Sam local PTY + filesystem — localhost WebSocket shell + /v1/fs HTTP.
|
|
3
|
+
* No tunnel, no IAM. Compatible with iam-pty wire format (raw bytes + JSON resize/slash).
|
|
4
4
|
*/
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import http from 'node:http';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { WebSocketServer } from 'ws';
|
|
10
|
+
import { createLocalFilesystem } from '../local-fs/index.js';
|
|
11
|
+
import { resolveContainedPath } from '../local-fs/paths.js';
|
|
12
|
+
import { getRepositoryFreshness } from '../local-fs/freshness.js';
|
|
13
|
+
import {
|
|
14
|
+
mintWorkspaceCapability,
|
|
15
|
+
writeLocalRuntimeRecord,
|
|
16
|
+
extractCapability,
|
|
17
|
+
isAllowedStudioOrigin,
|
|
18
|
+
isLoopbackRemote,
|
|
19
|
+
WORKSPACE_CAPABILITY_HEADER,
|
|
20
|
+
} from '../local-fs/capability.js';
|
|
10
21
|
|
|
11
22
|
const DEFAULT_PORT = 3099;
|
|
12
23
|
|
|
@@ -20,6 +31,49 @@ function shellForPlatform() {
|
|
|
20
31
|
return process.env.SHELL || '/bin/zsh';
|
|
21
32
|
}
|
|
22
33
|
|
|
34
|
+
function readJsonBody(req) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const chunks = [];
|
|
37
|
+
req.on('data', (c) => chunks.push(c));
|
|
38
|
+
req.on('end', () => {
|
|
39
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
40
|
+
if (!raw) return resolve({});
|
|
41
|
+
try {
|
|
42
|
+
resolve(JSON.parse(raw));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
reject(err);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
req.on('error', reject);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {import('http').IncomingMessage} req
|
|
53
|
+
* @param {import('http').ServerResponse} res
|
|
54
|
+
* @param {number} status
|
|
55
|
+
* @param {object} body
|
|
56
|
+
* @param {{ allowOrigin?: string|null }} [opts]
|
|
57
|
+
*/
|
|
58
|
+
function sendJson(req, res, status, body, opts = {}) {
|
|
59
|
+
const origin = typeof req.headers.origin === 'string' ? req.headers.origin : '';
|
|
60
|
+
const allowOrigin = opts.allowOrigin !== undefined
|
|
61
|
+
? opts.allowOrigin
|
|
62
|
+
: (isAllowedStudioOrigin(origin) ? (origin || null) : null);
|
|
63
|
+
/** @type {Record<string, string>} */
|
|
64
|
+
const headers = {
|
|
65
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
66
|
+
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
|
|
67
|
+
'Access-Control-Allow-Headers': `Content-Type, Authorization, ${WORKSPACE_CAPABILITY_HEADER}`,
|
|
68
|
+
};
|
|
69
|
+
if (allowOrigin) {
|
|
70
|
+
headers['Access-Control-Allow-Origin'] = allowOrigin;
|
|
71
|
+
headers.Vary = 'Origin';
|
|
72
|
+
}
|
|
73
|
+
res.writeHead(status, headers);
|
|
74
|
+
res.end(JSON.stringify(body));
|
|
75
|
+
}
|
|
76
|
+
|
|
23
77
|
export function ensureNodePtySpawnHelperExecutable(options = {}) {
|
|
24
78
|
const platform = options.platform || process.platform;
|
|
25
79
|
const arch = options.arch || process.arch;
|
|
@@ -130,26 +184,165 @@ export function attachLocalPtySession({ ws, pty, shell, cwd, cols = 80, rows = 2
|
|
|
130
184
|
*/
|
|
131
185
|
export async function startLocalPtyServer(opts = {}) {
|
|
132
186
|
const pty = await loadPty(opts.pty);
|
|
133
|
-
const cwd = opts.cwd || process.cwd();
|
|
187
|
+
const cwd = path.resolve(opts.cwd || process.cwd());
|
|
134
188
|
const requestedPort = opts.port === 0 ? 0 : parsePort(opts.port ?? process.env.PTY_PORT, DEFAULT_PORT);
|
|
135
189
|
const host = opts.host || '127.0.0.1';
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
190
|
+
if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') {
|
|
191
|
+
throw new Error('local PTY/FS must bind loopback only (127.0.0.1)');
|
|
192
|
+
}
|
|
193
|
+
const shell = opts.shell || shellForPlatform();
|
|
194
|
+
const filesystem = createLocalFilesystem(cwd);
|
|
195
|
+
// Capability minted after bind so port is known; provisional id first.
|
|
196
|
+
/** @type {ReturnType<typeof mintWorkspaceCapability>|null} */
|
|
197
|
+
let capability = null;
|
|
198
|
+
|
|
199
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
200
|
+
const url = new URL(req.url || '/', `http://${host}`);
|
|
201
|
+
const pathname = url.pathname;
|
|
202
|
+
const origin = typeof req.headers.origin === 'string' ? req.headers.origin : '';
|
|
203
|
+
|
|
204
|
+
if (req.method === 'OPTIONS') {
|
|
205
|
+
if (!isAllowedStudioOrigin(origin)) {
|
|
206
|
+
res.writeHead(403);
|
|
207
|
+
res.end();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
res.writeHead(204, {
|
|
211
|
+
'Access-Control-Allow-Origin': origin || 'http://127.0.0.1:8080',
|
|
212
|
+
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
|
|
213
|
+
'Access-Control-Allow-Headers': `Content-Type, Authorization, ${WORKSPACE_CAPABILITY_HEADER}`,
|
|
214
|
+
Vary: 'Origin',
|
|
215
|
+
});
|
|
216
|
+
res.end();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (pathname === '/health') {
|
|
221
|
+
sendJson(req, res, 200, {
|
|
222
|
+
ok: true,
|
|
223
|
+
service: 'agentsam-local-pty',
|
|
224
|
+
cwd,
|
|
225
|
+
root: cwd,
|
|
226
|
+
port: Number(httpServer.address()?.port || requestedPort),
|
|
227
|
+
shell,
|
|
228
|
+
filesystem: true,
|
|
229
|
+
filesystem_engine: filesystem.engine,
|
|
230
|
+
workspace_id: capability?.workspace_id || null,
|
|
231
|
+
capability_required: true,
|
|
232
|
+
binding: host,
|
|
233
|
+
});
|
|
151
234
|
return;
|
|
152
235
|
}
|
|
236
|
+
|
|
237
|
+
// Bootstrap: loopback-only claim of the authorized workspace capability.
|
|
238
|
+
// Does NOT accept an arbitrary root — root is always the process-bound cwd.
|
|
239
|
+
if (pathname === '/v1/workspace/bootstrap' && req.method === 'GET') {
|
|
240
|
+
if (!isLoopbackRemote(req)) {
|
|
241
|
+
return sendJson(req, res, 403, {
|
|
242
|
+
ok: false,
|
|
243
|
+
error: 'loopback_required',
|
|
244
|
+
code: 'WORKSPACE_BOOTSTRAP_DENIED',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (origin && !isAllowedStudioOrigin(origin)) {
|
|
248
|
+
return sendJson(req, res, 403, {
|
|
249
|
+
ok: false,
|
|
250
|
+
error: 'origin_not_allowed',
|
|
251
|
+
code: 'WORKSPACE_BOOTSTRAP_DENIED',
|
|
252
|
+
}, { allowOrigin: null });
|
|
253
|
+
}
|
|
254
|
+
const requestedRoot = url.searchParams.get('root');
|
|
255
|
+
if (requestedRoot) {
|
|
256
|
+
const resolved = path.resolve(requestedRoot);
|
|
257
|
+
if (resolved !== cwd) {
|
|
258
|
+
return sendJson(req, res, 403, {
|
|
259
|
+
ok: false,
|
|
260
|
+
error: 'root_not_authorized',
|
|
261
|
+
code: 'WORKSPACE_ROOT_MISMATCH',
|
|
262
|
+
message: 'Cannot claim an arbitrary path. Start the runtime from the desired workspace root.',
|
|
263
|
+
authorized_root: cwd,
|
|
264
|
+
requested_root: resolved,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return sendJson(req, res, 200, {
|
|
269
|
+
ok: true,
|
|
270
|
+
...capability,
|
|
271
|
+
freshness: getRepositoryFreshness(cwd),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (pathname === '/v1/freshness' && req.method === 'GET') {
|
|
276
|
+
const gate = requireCapability(req, res, capability);
|
|
277
|
+
if (!gate) return;
|
|
278
|
+
return sendJson(req, res, 200, getRepositoryFreshness(cwd));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (pathname === '/v1/fs' || pathname === '/v1/fs/') {
|
|
282
|
+
const gate = requireCapability(req, res, capability);
|
|
283
|
+
if (!gate) return;
|
|
284
|
+
return sendJson(req, res, 200, {
|
|
285
|
+
ok: true,
|
|
286
|
+
root: filesystem.root,
|
|
287
|
+
workspace_id: capability.workspace_id,
|
|
288
|
+
engine: filesystem.engine,
|
|
289
|
+
endpoints: ['list', 'stat', 'read', 'write', 'create', 'rename', 'remove', 'mkdir'],
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (pathname.startsWith('/v1/fs/')) {
|
|
294
|
+
const gate = requireCapability(req, res, capability);
|
|
295
|
+
if (!gate) return;
|
|
296
|
+
try {
|
|
297
|
+
const action = pathname.slice('/v1/fs/'.length).replace(/\/$/, '');
|
|
298
|
+
const qPath = url.searchParams.get('path') || '.';
|
|
299
|
+
if (req.method === 'GET' && action === 'list') {
|
|
300
|
+
const recursive = url.searchParams.get('recursive') === '1' || url.searchParams.get('recursive') === 'true';
|
|
301
|
+
return sendJson(req, res, 200, filesystem.list(qPath, { recursive, maxDepth: 5 }));
|
|
302
|
+
}
|
|
303
|
+
if (req.method === 'GET' && action === 'stat') {
|
|
304
|
+
return sendJson(req, res, 200, filesystem.stat(qPath));
|
|
305
|
+
}
|
|
306
|
+
if (req.method === 'GET' && action === 'read') {
|
|
307
|
+
return sendJson(req, res, 200, filesystem.read(qPath));
|
|
308
|
+
}
|
|
309
|
+
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'DELETE') {
|
|
310
|
+
const body = await readJsonBody(req);
|
|
311
|
+
if (action === 'write') {
|
|
312
|
+
return sendJson(req, res, 200, filesystem.write(body.path || qPath, body.content ?? '', {
|
|
313
|
+
expectedVersion: body.expectedVersion ?? body.expected_version,
|
|
314
|
+
overwrite: Boolean(body.overwrite),
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
if (action === 'create') {
|
|
318
|
+
return sendJson(req, res, 200, filesystem.create(body.path || qPath, body.content ?? ''));
|
|
319
|
+
}
|
|
320
|
+
if (action === 'rename') {
|
|
321
|
+
return sendJson(req, res, 200, filesystem.rename(body.from || qPath, body.to, {
|
|
322
|
+
expectedVersion: body.expectedVersion ?? body.expected_version,
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
if (action === 'remove' || action === 'delete') {
|
|
326
|
+
return sendJson(req, res, 200, filesystem.remove(body.path || qPath, {
|
|
327
|
+
expectedVersion: body.expectedVersion ?? body.expected_version,
|
|
328
|
+
recursive: Boolean(body.recursive),
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
if (action === 'mkdir') {
|
|
332
|
+
return sendJson(req, res, 200, filesystem.mkdir(body.path || qPath));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
sendJson(req, res, 404, { ok: false, error: 'unknown_fs_action', action });
|
|
336
|
+
} catch (err) {
|
|
337
|
+
sendJson(req, res, 400, {
|
|
338
|
+
ok: false,
|
|
339
|
+
error: err instanceof Error ? err.message : String(err),
|
|
340
|
+
code: 'fs_request_failed',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
153
346
|
res.writeHead(404);
|
|
154
347
|
res.end('not found');
|
|
155
348
|
});
|
|
@@ -158,9 +351,47 @@ export async function startLocalPtyServer(opts = {}) {
|
|
|
158
351
|
|
|
159
352
|
wss.on('connection', (ws, req) => {
|
|
160
353
|
const url = new URL(req.url || '/', `http://${host}`);
|
|
161
|
-
const
|
|
354
|
+
const cap = extractCapability(req, capability?.capability);
|
|
355
|
+
const qCap = url.searchParams.get('capability') || '';
|
|
356
|
+
const okCap = (capability && qCap && qCap === capability.capability) || cap.ok;
|
|
357
|
+
if (!okCap) {
|
|
358
|
+
ws.send(JSON.stringify({
|
|
359
|
+
type: 'error',
|
|
360
|
+
code: 'WORKSPACE_CAPABILITY_REQUIRED',
|
|
361
|
+
message: 'PTY attach requires workspace capability from /v1/workspace/bootstrap',
|
|
362
|
+
}));
|
|
363
|
+
ws.close();
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Session cwd must stay inside the authorized filesystem root — never escape.
|
|
368
|
+
let sessionCwd = cwd;
|
|
369
|
+
const requestedCwd = url.searchParams.get('cwd')?.trim();
|
|
370
|
+
if (requestedCwd) {
|
|
371
|
+
const abs = path.isAbsolute(requestedCwd) ? path.resolve(requestedCwd) : path.resolve(cwd, requestedCwd);
|
|
372
|
+
if (abs !== cwd) {
|
|
373
|
+
const rel = path.relative(cwd, abs);
|
|
374
|
+
const contained = resolveContainedPath(cwd, rel || '.');
|
|
375
|
+
if (!contained.ok) {
|
|
376
|
+
ws.send(JSON.stringify({
|
|
377
|
+
type: 'error',
|
|
378
|
+
code: 'WORKSPACE_ROOT_MISMATCH',
|
|
379
|
+
message: 'PTY cwd must equal the authorized workspace root (or a contained path).',
|
|
380
|
+
authorized_root: cwd,
|
|
381
|
+
requested: abs,
|
|
382
|
+
}));
|
|
383
|
+
ws.close();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
sessionCwd = contained.abs;
|
|
387
|
+
} else {
|
|
388
|
+
sessionCwd = cwd;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
162
392
|
const cols = parsePort(url.searchParams.get('cols'), 80);
|
|
163
393
|
const rows = parsePort(url.searchParams.get('rows'), 24);
|
|
394
|
+
const sessionId = `pty_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
164
395
|
|
|
165
396
|
attachLocalPtySession({
|
|
166
397
|
ws,
|
|
@@ -170,7 +401,17 @@ export async function startLocalPtyServer(opts = {}) {
|
|
|
170
401
|
cols,
|
|
171
402
|
rows,
|
|
172
403
|
env: process.env,
|
|
404
|
+
sessionId,
|
|
173
405
|
});
|
|
406
|
+
|
|
407
|
+
ws.send(JSON.stringify({
|
|
408
|
+
type: 'workspace_identity',
|
|
409
|
+
workspace_id: capability.workspace_id,
|
|
410
|
+
root: cwd,
|
|
411
|
+
session_cwd: sessionCwd,
|
|
412
|
+
session_id: sessionId,
|
|
413
|
+
runtimeBaseUrl: capability.runtimeBaseUrl,
|
|
414
|
+
}));
|
|
174
415
|
});
|
|
175
416
|
|
|
176
417
|
await new Promise((resolve) => {
|
|
@@ -178,13 +419,22 @@ export async function startLocalPtyServer(opts = {}) {
|
|
|
178
419
|
});
|
|
179
420
|
const boundPort = Number(httpServer.address()?.port || requestedPort);
|
|
180
421
|
|
|
422
|
+
capability = mintWorkspaceCapability({ root: cwd, port: boundPort, host });
|
|
423
|
+
const runtimeFile = writeLocalRuntimeRecord(capability);
|
|
424
|
+
|
|
181
425
|
return {
|
|
182
426
|
port: boundPort,
|
|
183
427
|
host,
|
|
184
428
|
cwd,
|
|
185
429
|
shell,
|
|
430
|
+
filesystem,
|
|
431
|
+
capability,
|
|
432
|
+
runtimeFile,
|
|
433
|
+
workspace_id: capability.workspace_id,
|
|
186
434
|
url: `ws://${host}:${boundPort}`,
|
|
187
435
|
healthUrl: `http://${host}:${boundPort}/health`,
|
|
436
|
+
fsBaseUrl: `http://${host}:${boundPort}/v1/fs`,
|
|
437
|
+
bootstrapUrl: `http://${host}:${boundPort}/v1/workspace/bootstrap`,
|
|
188
438
|
close: () =>
|
|
189
439
|
new Promise((resolve, reject) => {
|
|
190
440
|
wss.close(() => {
|
|
@@ -193,3 +443,29 @@ export async function startLocalPtyServer(opts = {}) {
|
|
|
193
443
|
}),
|
|
194
444
|
};
|
|
195
445
|
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* @param {import('http').IncomingMessage} req
|
|
449
|
+
* @param {import('http').ServerResponse} res
|
|
450
|
+
* @param {ReturnType<typeof mintWorkspaceCapability>|null} capability
|
|
451
|
+
*/
|
|
452
|
+
function requireCapability(req, res, capability) {
|
|
453
|
+
if (!capability) {
|
|
454
|
+
sendJson(req, res, 503, { ok: false, error: 'capability_not_ready', code: 'WORKSPACE_CAPABILITY_REQUIRED' });
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
const gate = extractCapability(req, capability.capability);
|
|
458
|
+
if (!gate.ok) {
|
|
459
|
+
sendJson(req, res, 401, {
|
|
460
|
+
ok: false,
|
|
461
|
+
error: 'workspace_capability_required',
|
|
462
|
+
code: 'WORKSPACE_CAPABILITY_REQUIRED',
|
|
463
|
+
authorization: {
|
|
464
|
+
required: ['workspace.capability'],
|
|
465
|
+
bootstrap: '/v1/workspace/bootstrap',
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
return true;
|
|
471
|
+
}
|
package/src/mcp/client.js
CHANGED
|
@@ -8,7 +8,7 @@ function clean(value) {
|
|
|
8
8
|
export function buildHeaders(serverConfig = {}) {
|
|
9
9
|
const headers = {
|
|
10
10
|
'Accept': 'application/json, text/event-stream',
|
|
11
|
-
'User-Agent': 'AgentSam-SDK/2.6.
|
|
11
|
+
'User-Agent': 'AgentSam-SDK/2.6.3 (MCP Client)',
|
|
12
12
|
};
|
|
13
13
|
const token = clean(serverConfig.auth?.token);
|
|
14
14
|
if (token) {
|
|
@@ -103,7 +103,7 @@ export async function listMcpTools(serverConfig = {}, options = {}) {
|
|
|
103
103
|
const transport = new SSEClientTransport(new URL(url), {
|
|
104
104
|
requestInit: { headers },
|
|
105
105
|
});
|
|
106
|
-
const client = new Client({ name: 'agentsam-sdk', version: '2.6.
|
|
106
|
+
const client = new Client({ name: 'agentsam-sdk', version: '2.6.3' }, { capabilities: {} });
|
|
107
107
|
await client.connect(transport);
|
|
108
108
|
const toolsResult = await client.listTools();
|
|
109
109
|
await transport.close();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding guidance when a user has no usable AI path yet.
|
|
3
|
+
*
|
|
4
|
+
* Paths (any one unlocks AgentSam model options):
|
|
5
|
+
* 1. Local Ollama + at least one model
|
|
6
|
+
* 2. Provider API key (OpenAI / Anthropic / Gemini / …) → their allotted models
|
|
7
|
+
* 3. Signed-in Local Studio identity + Cloudflare OAuth (Workers AI / account tools)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const LOCAL_STUDIO_ORIGIN = 'https://agentsam.inneranimalmedia.com';
|
|
11
|
+
|
|
12
|
+
/** Identity login (Sign in with Cloudflare) — PKCE public client. */
|
|
13
|
+
export const CF_OAUTH_LOGIN_START = `${LOCAL_STUDIO_ORIGIN}/api/oauth/cloudflare/start?next=/agentsam`;
|
|
14
|
+
|
|
15
|
+
/** Resource connector (MCP / Workers / D1) — same CF OAuth client, different callback. */
|
|
16
|
+
export const CF_OAUTH_CONNECT_START = `${LOCAL_STUDIO_ORIGIN}/api/connections/cloudflare/start`;
|
|
17
|
+
|
|
18
|
+
/** Login portal with explicit next (desktop / CLI). */
|
|
19
|
+
export const LOCAL_STUDIO_LOGIN = `${LOCAL_STUDIO_ORIGIN}/auth/login?next=/agentsam`;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Redirect URIs that must be registered on the Cloudflare OAuth client
|
|
23
|
+
* "AgentSam Local Studio" (PKCE, Token Authentication Method = None).
|
|
24
|
+
*/
|
|
25
|
+
export const CF_OAUTH_REQUIRED_REDIRECTS = Object.freeze([
|
|
26
|
+
`${LOCAL_STUDIO_ORIGIN}/api/oauth/cloudflare/callback`,
|
|
27
|
+
`${LOCAL_STUDIO_ORIGIN}/api/connections/cloudflare/callback`,
|
|
28
|
+
'http://localhost:3000/api/connections/cloudflare/callback',
|
|
29
|
+
'http://localhost:3000/api/oauth/cloudflare/callback',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {{ inventory?: object, ollama?: object, providers?: object[] }} discovered
|
|
34
|
+
* Shape compatible with discoverIngestModelOptions() / collectModelsStatus().
|
|
35
|
+
*/
|
|
36
|
+
export function assessAiAccess(discovered = {}) {
|
|
37
|
+
const providers = discovered.inventory?.providers
|
|
38
|
+
|| discovered.providers
|
|
39
|
+
|| [];
|
|
40
|
+
const configuredProviders = providers.filter((p) => p?.configured === true);
|
|
41
|
+
const ollamaOnline = Boolean(discovered.ollama?.online || discovered.inventory?.local?.online);
|
|
42
|
+
const ollamaModels = discovered.ollama?.models || discovered.inventory?.local?.models || [];
|
|
43
|
+
const hasOllamaModels = ollamaOnline && Array.isArray(ollamaModels) && ollamaModels.length > 0;
|
|
44
|
+
const hasProviderKeys = configuredProviders.length > 0;
|
|
45
|
+
const embedOptions = Array.isArray(discovered.options)
|
|
46
|
+
? discovered.options.filter((o) => o.value && o.value !== 'none')
|
|
47
|
+
: [];
|
|
48
|
+
|
|
49
|
+
const ready = hasOllamaModels || hasProviderKeys || embedOptions.length > 0;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
ready,
|
|
53
|
+
hasOllamaModels,
|
|
54
|
+
hasProviderKeys,
|
|
55
|
+
configuredProviders: configuredProviders.map((p) => p.id),
|
|
56
|
+
ollamaOnline,
|
|
57
|
+
ollamaModelCount: hasOllamaModels ? ollamaModels.length : 0,
|
|
58
|
+
embedOptionCount: embedOptions.length,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Human-readable onboarding block for CLI notes / stderr.
|
|
64
|
+
* @param {ReturnType<typeof assessAiAccess>} access
|
|
65
|
+
* @param {{ wantCloudAi?: boolean }} [opts]
|
|
66
|
+
*/
|
|
67
|
+
export function formatAiAccessOnboarding(access, opts = {}) {
|
|
68
|
+
const lines = [];
|
|
69
|
+
if (access.ready) {
|
|
70
|
+
lines.push('AI access: ready');
|
|
71
|
+
if (access.hasProviderKeys) {
|
|
72
|
+
lines.push(` providers ${access.configuredProviders.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
if (access.hasOllamaModels) {
|
|
75
|
+
lines.push(` ollama online · ${access.ollamaModelCount} model(s)`);
|
|
76
|
+
}
|
|
77
|
+
if (access.embedOptionCount) {
|
|
78
|
+
lines.push(` embed menu ${access.embedOptionCount} option(s)`);
|
|
79
|
+
}
|
|
80
|
+
return lines.join('\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
lines.push('AI access: not configured yet');
|
|
84
|
+
lines.push('');
|
|
85
|
+
lines.push('Pick ONE path to unlock AgentSam model options:');
|
|
86
|
+
lines.push('');
|
|
87
|
+
lines.push(' 1) Local models (Ollama) — free, offline');
|
|
88
|
+
lines.push(' https://ollama.com/download');
|
|
89
|
+
lines.push(' ollama pull mxbai-embed-large');
|
|
90
|
+
lines.push(' ollama pull qwen2.5-coder');
|
|
91
|
+
lines.push(' agentsam ollama status');
|
|
92
|
+
lines.push('');
|
|
93
|
+
lines.push(' 2) Provider API key — use models your key is allotted');
|
|
94
|
+
lines.push(' agentsam providers');
|
|
95
|
+
lines.push(' (OpenAI · Anthropic · Gemini · Cursor · xAI · Cloudflare token)');
|
|
96
|
+
lines.push('');
|
|
97
|
+
lines.push(' 3) Local Studio identity + Cloudflare OAuth');
|
|
98
|
+
lines.push(' (Workers AI / account tools / MCP connector)');
|
|
99
|
+
lines.push(` Login: ${LOCAL_STUDIO_LOGIN}`);
|
|
100
|
+
lines.push(` CF sign-in: ${CF_OAUTH_LOGIN_START}`);
|
|
101
|
+
lines.push(` CF connect: ${CF_OAUTH_CONNECT_START}`);
|
|
102
|
+
if (opts.wantCloudAi) {
|
|
103
|
+
lines.push('');
|
|
104
|
+
lines.push(' Cloudflare OAuth client must allow these redirect URIs:');
|
|
105
|
+
for (const uri of CF_OAUTH_REQUIRED_REDIRECTS) {
|
|
106
|
+
lines.push(` • ${uri}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push('AST/text-only indexing still works with embedding: none ($0).');
|
|
111
|
+
return lines.join('\n');
|
|
112
|
+
}
|
package/src/models/discovery.js
CHANGED
|
@@ -315,11 +315,19 @@ export async function discoverCloudflareModels(apiToken, accountId, fetchImpl =
|
|
|
315
315
|
.map((row) => {
|
|
316
316
|
const id = clean(row?.name);
|
|
317
317
|
const task = cloudflareTaskName(row?.task);
|
|
318
|
-
|
|
318
|
+
const taskLower = task.toLowerCase();
|
|
319
|
+
// Text Generation = chat/agent pool. Text Embeddings = Vectorize / Workers AI embed pool.
|
|
320
|
+
// Never drop embeddings here — chat allowlisting happens in inventory curation only.
|
|
321
|
+
if (!id || (taskLower !== 'text generation' && taskLower !== 'text embeddings')) return null;
|
|
322
|
+
const isEmbed = taskLower === 'text embeddings';
|
|
319
323
|
return baseRecord('cloudflare', id, {
|
|
320
324
|
label: id,
|
|
321
325
|
source_url: url,
|
|
322
|
-
capabilities: {
|
|
326
|
+
capabilities: {
|
|
327
|
+
workers_ai: true,
|
|
328
|
+
embeddings: isEmbed,
|
|
329
|
+
agent_runtime: !isEmbed,
|
|
330
|
+
},
|
|
323
331
|
metadata: {
|
|
324
332
|
task,
|
|
325
333
|
author: clean(row?.author) || null,
|
|
@@ -42,7 +42,15 @@ export function providerIdForService(serviceName) {
|
|
|
42
42
|
|
|
43
43
|
export function filterWorkersAiCurated(models = [], options = {}) {
|
|
44
44
|
if (options.curated === false) return [...models];
|
|
45
|
-
|
|
45
|
+
// Chat/agent pool: curated allowlist. Embedding models always pass —
|
|
46
|
+
// Vectorize / Workers AI embed lanes must not be limited by the chat allowlist.
|
|
47
|
+
return models.filter((row) => {
|
|
48
|
+
const id = String(row?.provider_model_id || row?.model_id || '');
|
|
49
|
+
const caps = row?.capabilities || {};
|
|
50
|
+
const task = String(row?.metadata?.task || '').toLowerCase();
|
|
51
|
+
if (caps.embeddings === true || task === 'text embeddings' || /embed/i.test(id)) return true;
|
|
52
|
+
return CURATED.has(id);
|
|
53
|
+
});
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
/**
|