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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 7.15.3
- 新增沙箱(Sandbox)模块,支持沙箱实例、文件系统、命令执行、Git、PTY、模板、资源挂载、请求注入等能力
- 新增沙箱模块的 TypeScript 类型声明、示例和测试覆盖
- 沙箱请求注入支持 `baseUrl` 路径前缀匹配和 `ifHeaders`、`ifQueries` 条件匹配

## 7.15.2
- 对象存储,修复 pfop pipeline 参数无效问题
Expand Down
12 changes: 9 additions & 3 deletions examples/sandbox_injection_rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ runExample(() => {
name: ruleName,
injection: {
type: 'http',
baseUrl: 'https://httpbin.org',
baseUrl: 'https://httpbin.org/bearer',
ifHeaders: {
'X-Sandbox-Example': 'qiniu-nodejs-sdk'
},
headers: {
Authorization: `Bearer ${env('QINIU_SANDBOX_HTTP_INJECTION_TOKEN', 'real_token')}`
}
Expand All @@ -38,7 +41,10 @@ runExample(() => {
name: `${ruleName}-updated`,
injection: {
type: 'http',
baseUrl: 'https://httpbin.org',
baseUrl: 'https://httpbin.org/bearer',
ifHeaders: {
'X-Sandbox-Example': 'qiniu-nodejs-sdk'
},
headers: {
Authorization: `Bearer ${env('QINIU_SANDBOX_HTTP_INJECTION_TOKEN', 'updated_token')}`,
'X-Sandbox-Example': 'qiniu-nodejs-sdk'
Expand All @@ -64,7 +70,7 @@ runExample(() => {
});
}).then(created => {
sandbox = created;
return sandbox.commands.run('curl --max-time 20 -sSL https://httpbin.org/bearer -H "Authorization: Bearer fake_token"', {
return sandbox.commands.run('curl --max-time 20 -sSL https://httpbin.org/bearer -H "Authorization: Bearer fake_token" -H "X-Sandbox-Example: qiniu-nodejs-sdk"', {
timeout: 30000
});
}).then(result => {
Expand Down
7 changes: 5 additions & 2 deletions examples/sandbox_request_injections.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ runExample(() => {
injections: [
{
type: 'http',
base_url: 'https://httpbin.org',
baseUrl: 'https://httpbin.org/bearer',
ifHeaders: {
'X-Sandbox-Example': 'qiniu-nodejs-sdk'
},
headers: {
Authorization: `Bearer ${env('QINIU_SANDBOX_HTTP_INJECTION_TOKEN', 'real_token')}`
}
Expand All @@ -23,7 +26,7 @@ runExample(() => {
}
}).then(created => {
sandbox = created;
return sandbox.commands.run('curl --max-time 20 -sSL https://httpbin.org/bearer -H "Authorization: Bearer fake_token"', {
return sandbox.commands.run('curl --max-time 20 -sSL https://httpbin.org/bearer -H "Authorization: Bearer fake_token" -H "X-Sandbox-Example: qiniu-nodejs-sdk"', {
timeout: 30000
});
}).then(result => {
Expand Down
89 changes: 84 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,85 @@ export declare namespace sandbox {

type SandboxResource = GitRepositoryResource | KodoResource;

type InjectionConditions = {[key: string]: string};

interface InjectionMatchOptions {
base_url?: string;
baseUrl?: string;
if_headers?: InjectionConditions;
ifHeaders?: InjectionConditions;
if_queries?: InjectionConditions;
ifQueries?: InjectionConditions;
}

interface InjectionById {
type: 'id';
id?: string;
ruleID?: string;
ruleId?: string;
injectionRuleID?: string;
}

interface InjectionRuleReference {
injectionRuleID: string;
}

interface HttpInjection extends InjectionMatchOptions {
type: 'http';
headers?: {[key: string]: string};
}

interface OpenaiInjection extends InjectionMatchOptions {
type: 'openai';
api_key?: string;
apiKey?: string;
}

interface AnthropicInjection extends InjectionMatchOptions {
type: 'anthropic';
api_key?: string;
apiKey?: string;
}

interface GeminiInjection extends InjectionMatchOptions {
type: 'gemini';
api_key?: string;
apiKey?: string;
}

interface QiniuInjection extends InjectionMatchOptions {
type: 'qiniu';
api_key?: string;
apiKey?: string;
}

interface GithubInjection extends InjectionMatchOptions {
type: 'github';
token: string;
}

type Injection = HttpInjection | OpenaiInjection | AnthropicInjection | GeminiInjection | QiniuInjection | GithubInjection;
type SandboxInjection = InjectionById | InjectionRuleReference | Injection;

interface InjectionRule {
ruleID?: string;
id?: string;
name: string;
createdAt?: string | Date;
updatedAt?: string | Date;
injection: Injection;
}

interface CreateInjectionRuleOptions {
name: string;
injection: Injection;
}

interface UpdateInjectionRuleOptions {
name?: string;
injection?: Injection;
}

interface SandboxCreateOptions extends SandboxClientOptions {
template?: string;
templateID?: string;
Expand All @@ -84,7 +163,7 @@ export declare namespace sandbox {
envVars?: {[key: string]: string};
envs?: {[key: string]: string};
mcp?: any;
injections?: any[];
injections?: SandboxInjection[];
resources?: SandboxResource[];
client?: SandboxClient;
}
Expand Down Expand Up @@ -403,10 +482,10 @@ export declare namespace sandbox {
deleteTemplateTags(options?: any): Promise<null>;
getTemplateByAlias(alias: string): Promise<any>;

listInjectionRules(): Promise<any>;
createInjectionRule(options?: any): Promise<any>;
getInjectionRule(ruleID: string): Promise<any>;
updateInjectionRule(ruleID: string, options?: any): Promise<any>;
listInjectionRules(): Promise<InjectionRule[]>;
createInjectionRule(options: CreateInjectionRuleOptions): Promise<InjectionRule>;
getInjectionRule(ruleID: string): Promise<InjectionRule>;
updateInjectionRule(ruleID: string, options: UpdateInjectionRuleOptions): Promise<InjectionRule>;
deleteInjectionRule(ruleID: string): Promise<null>;

create(options?: SandboxCreateOptions): Promise<any>;
Expand Down
11 changes: 11 additions & 0 deletions qiniu/sandbox/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ function normalizeInjection (injection) {
normalized.base_url = normalized.baseUrl;
delete normalized.baseUrl;
}
if (normalized.ifHeaders !== undefined && normalized.if_headers === undefined) {
normalized.if_headers = normalized.ifHeaders;
delete normalized.ifHeaders;
}
if (normalized.ifQueries !== undefined && normalized.if_queries === undefined) {
normalized.if_queries = normalized.ifQueries;
delete normalized.ifQueries;
}
if (normalized.ruleId !== undefined && normalized.ruleID === undefined) {
normalized.ruleID = normalized.ruleId;
delete normalized.ruleId;
Expand Down Expand Up @@ -172,6 +180,9 @@ SandboxClient.prototype._request = function (method, path, options) {
const body = options.body;
const hasBody = body !== undefined && body !== null;
const headers = this._headers(options.authType);
if (!hasBody && (method === 'GET' || method === 'HEAD')) {
delete headers['Content-Type'];
}
const urllibOptions = {
method,
headers,
Expand Down
62 changes: 60 additions & 2 deletions test/sandbox_client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ describe('test sandbox client module', function () {
type: 'qiniu',
apiKey: 'ak',
baseUrl: 'https://example.com',
ifHeaders: {
'X-Provider': 'qiniu'
},
ifQueries: {
model: 'default'
},
ruleId: 'rule_1'
}
]
Expand All @@ -62,6 +68,12 @@ describe('test sandbox client module', function () {
type: 'qiniu',
api_key: 'ak',
base_url: 'https://example.com',
if_headers: {
'X-Provider': 'qiniu'
},
if_queries: {
model: 'default'
},
ruleID: 'rule_1'
}
]
Expand Down Expand Up @@ -386,7 +398,14 @@ describe('test sandbox client module', function () {
name: 'openai',
injection: {
type: 'openai',
apiKey: 'secret'
apiKey: 'secret',
baseUrl: 'https://api.openai.com/v1/*',
ifHeaders: {
'X-Use-Injected-Key': 'true'
},
ifQueries: {
source: 'sdk-test'
}
}
}).then(() => {
fixture.requests[0].method.should.eql('POST');
Expand All @@ -397,7 +416,14 @@ describe('test sandbox client module', function () {
name: 'openai',
injection: {
type: 'openai',
api_key: 'secret'
api_key: 'secret',
base_url: 'https://api.openai.com/v1/*',
if_headers: {
'X-Use-Injected-Key': 'true'
},
if_queries: {
source: 'sdk-test'
}
}
});
}).then(() => closeServer(fixture.server), err => {
Expand All @@ -408,6 +434,38 @@ describe('test sandbox client module', function () {
});
});

it('uses the signing default content type for Qiniu-auth GET requests without a body', function () {
return startServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
id: 'rule_1',
name: 'openai',
injection: {
type: 'openai'
}
}));
}).then(fixture => {
const client = new qiniu.sandbox.SandboxClient({
endpoint: fixture.endpoint,
mac: new qiniu.auth.digest.Mac('ak', 'sk', {
disableQiniuTimestampSignature: true
})
});

return client.getInjectionRule('rule_1').then(() => {
fixture.requests[0].method.should.eql('GET');
fixture.requests[0].url.should.eql('/injection-rules/rule_1');
should(fixture.requests[0].headers.authorization).startWith('Qiniu ak:');
fixture.requests[0].headers['content-type'].should.eql('application/x-www-form-urlencoded');
}).then(() => closeServer(fixture.server), err => {
return closeServer(fixture.server).then(() => {
throw err;
});
});
});
});

it('maps sandbox lifecycle and metrics APIs', function () {
return startServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
Expand Down
28 changes: 28 additions & 0 deletions test/sandbox_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,38 @@ async function useSandboxTypes () {
mount_path: '/workspace/repo',
authorization_token: 'token'
};
const qiniuInjection: qiniu.sandbox.QiniuInjection = {
type: 'qiniu',
apiKey: 'sandbox-qiniu-ai-key',
baseUrl: 'https://api.qnaigc.com/v1/*',
ifHeaders: {
'X-Use-Injected-Key': 'true'
},
ifQueries: {
model: 'qiniu-default'
}
};
const githubInjection: qiniu.sandbox.GithubInjection = {
type: 'github',
token: 'github-token',
base_url: 'https://api.github.com/repos/qiniu/*',
if_headers: {
Accept: 'application/vnd.github+json'
},
if_queries: {
per_page: '100'
}
};

const sandbox = await qiniu.Sandbox.create('base', {
client,
resources: [kodoResource, gitResource],
injections: [
{ type: 'id', id: 'rule_1' },
{ injectionRuleID: 'rule_2' },
qiniuInjection,
githubInjection
],
network: {
allowOut: [qiniu.sandbox.ALL_TRAFFIC]
}
Expand Down
Loading