Implement GetDel method for space management in JS API
- Added a new GetDel method to the space object, allowing retrieval and immediate deletion of a key's value, enhancing one-time use data handling. - Updated the JS API to include tests for GetDel functionality, ensuring correct behavior for existing and non-existent keys, as well as complex data structures. - Removed debug print statements from the Assistant's initializeCapabilities method for cleaner code.
This commit is contained in:
parent
666c1e8d74
commit
a683452d22
3 changed files with 279 additions and 5 deletions
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/yaoapp/gou/connector"
|
||||
"github.com/yaoapp/gou/connector/openai"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/kun/utils"
|
||||
"github.com/yaoapp/yao/agent/assistant/handlers"
|
||||
"github.com/yaoapp/yao/agent/context"
|
||||
"github.com/yaoapp/yao/agent/i18n"
|
||||
|
|
@ -472,10 +471,6 @@ func (ast *Assistant) initializeCapabilities(ctx *context.Context, opts *context
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Println("--- initializeCapabilities debug ---")
|
||||
utils.Dump(capabilities)
|
||||
fmt.Println("--- end initializeCapabilities debug ---")
|
||||
|
||||
// Set capabilities in context for output adapters to use
|
||||
if capabilities != nil {
|
||||
ctx.Capabilities = capabilities
|
||||
|
|
|
|||
|
|
@ -694,6 +694,40 @@ func (ctx *Context) createSpaceObject(v8ctx *v8go.Context) *v8go.Value {
|
|||
delFuncVal := delFunc.GetFunction(v8ctx)
|
||||
obj.Set("Delete", delFuncVal.Value)
|
||||
|
||||
// GetDel method: space.GetDel(key) - Get value and delete immediately
|
||||
// Convenient for one-time use data (e.g., file metadata passed between agents)
|
||||
getDelFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
if ctx.Space == nil {
|
||||
return v8go.Null(iso)
|
||||
}
|
||||
|
||||
if len(info.Args()) < 1 {
|
||||
return bridge.JsException(info.Context(), "GetDel requires a key argument")
|
||||
}
|
||||
|
||||
key := info.Args()[0].String()
|
||||
|
||||
// Get value first
|
||||
value, err := ctx.Space.Get(key)
|
||||
if err != nil {
|
||||
return v8go.Null(iso)
|
||||
}
|
||||
|
||||
// Delete immediately after getting
|
||||
// Ignore delete errors (key might not exist)
|
||||
ctx.Space.Delete(key)
|
||||
|
||||
// Convert to JavaScript value
|
||||
jsValue, err := bridge.JsValue(info.Context(), value)
|
||||
if err != nil {
|
||||
return v8go.Null(iso)
|
||||
}
|
||||
|
||||
return jsValue
|
||||
})
|
||||
getDelFuncVal := getDelFunc.GetFunction(v8ctx)
|
||||
obj.Set("GetDel", getDelFuncVal.Value)
|
||||
|
||||
return spaceObj
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -570,3 +570,248 @@ func TestSpaceErrorHandling(t *testing.T) {
|
|||
|
||||
assert.Equal(t, true, result["success"], "Should catch Delete error")
|
||||
}
|
||||
|
||||
// TestSpaceGetDel tests ctx.space.GetDel method
|
||||
func TestSpaceGetDel(t *testing.T) {
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
ctx := &context.Context{
|
||||
ChatID: "test-chat-id",
|
||||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Space: plan.NewMemorySharedSpace(),
|
||||
}
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
function test(ctx) {
|
||||
try {
|
||||
// Set a one-time use value
|
||||
ctx.space.Set("one_time_token", "secret_token_12345");
|
||||
|
||||
// Verify it exists before GetDel
|
||||
const before = ctx.space.Get("one_time_token");
|
||||
if (before !== "secret_token_12345") throw new Error("Value not set");
|
||||
|
||||
// Use GetDel - should get value and delete automatically
|
||||
const value = ctx.space.GetDel("one_time_token");
|
||||
|
||||
// Verify value was retrieved
|
||||
if (value !== "secret_token_12345") throw new Error("GetDel returned wrong value");
|
||||
|
||||
// Verify key was deleted
|
||||
const after = ctx.space.Get("one_time_token");
|
||||
if (after !== null && after !== undefined) throw new Error("Key should be deleted after GetDel");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
value: value,
|
||||
is_deleted: after === null || after === undefined
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}`, ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
|
||||
result, ok := res.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map result, got %T", res)
|
||||
}
|
||||
|
||||
if !result["success"].(bool) {
|
||||
t.Fatalf("Test failed: %v", result["error"])
|
||||
}
|
||||
|
||||
assert.Equal(t, true, result["success"], "GetDel should succeed")
|
||||
assert.Equal(t, "secret_token_12345", result["value"], "GetDel should return correct value")
|
||||
assert.Equal(t, true, result["is_deleted"], "Key should be deleted after GetDel")
|
||||
}
|
||||
|
||||
// TestSpaceGetDelNonExistentKey tests GetDel on non-existent key
|
||||
func TestSpaceGetDelNonExistentKey(t *testing.T) {
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
ctx := &context.Context{
|
||||
ChatID: "test-chat-id",
|
||||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Space: plan.NewMemorySharedSpace(),
|
||||
}
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
function test(ctx) {
|
||||
try {
|
||||
// GetDel on non-existent key should return null/undefined
|
||||
const value = ctx.space.GetDel("non_existent_key");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
value: value,
|
||||
is_null: value === null || value === undefined
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}`, ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
|
||||
result, ok := res.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map result, got %T", res)
|
||||
}
|
||||
|
||||
assert.Equal(t, true, result["success"], "GetDel on non-existent key should not throw")
|
||||
assert.Equal(t, true, result["is_null"], "GetDel on non-existent key should return null")
|
||||
}
|
||||
|
||||
// TestSpaceGetDelComplexData tests GetDel with complex data structures
|
||||
func TestSpaceGetDelComplexData(t *testing.T) {
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
ctx := &context.Context{
|
||||
ChatID: "test-chat-id",
|
||||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Space: plan.NewMemorySharedSpace(),
|
||||
}
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
function test(ctx) {
|
||||
try {
|
||||
// Set complex file info data (like voucher assistant use case)
|
||||
const filesInfo = [
|
||||
{
|
||||
file_id: "file123",
|
||||
filename: "invoice.pdf",
|
||||
content_type: "application/pdf",
|
||||
file_type: "pdf",
|
||||
source: "uploader",
|
||||
uploader_name: "__yao.attachment"
|
||||
},
|
||||
{
|
||||
file_id: "file456",
|
||||
filename: "receipt.png",
|
||||
content_type: "image/png",
|
||||
file_type: "image",
|
||||
source: "uploader",
|
||||
uploader_name: "__yao.attachment"
|
||||
}
|
||||
];
|
||||
|
||||
ctx.space.Set("workers.voucher:files_info", filesInfo);
|
||||
|
||||
// Use GetDel to retrieve and clean up
|
||||
const retrieved = ctx.space.GetDel("workers.voucher:files_info");
|
||||
|
||||
// Verify data integrity
|
||||
if (!Array.isArray(retrieved)) throw new Error("Should be array");
|
||||
if (retrieved.length !== 2) throw new Error("Length mismatch");
|
||||
if (retrieved[0].file_id !== "file123") throw new Error("First file_id mismatch");
|
||||
if (retrieved[1].filename !== "receipt.png") throw new Error("Second filename mismatch");
|
||||
|
||||
// Verify it's deleted
|
||||
const after = ctx.space.Get("workers.voucher:files_info");
|
||||
if (after !== null && after !== undefined) throw new Error("Should be deleted");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
files_count: retrieved.length,
|
||||
first_file_id: retrieved[0].file_id,
|
||||
is_deleted: after === null || after === undefined
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}`, ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
|
||||
result, ok := res.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map result, got %T", res)
|
||||
}
|
||||
|
||||
if !result["success"].(bool) {
|
||||
t.Fatalf("Test failed: %v", result["error"])
|
||||
}
|
||||
|
||||
assert.Equal(t, true, result["success"], "GetDel with complex data should succeed")
|
||||
assert.Equal(t, float64(2), result["files_count"], "Should have 2 files")
|
||||
assert.Equal(t, "file123", result["first_file_id"], "File ID should match")
|
||||
assert.Equal(t, true, result["is_deleted"], "Should be deleted after GetDel")
|
||||
}
|
||||
|
||||
// TestSpaceGetDelMultipleCalls tests that GetDel only works once
|
||||
func TestSpaceGetDelMultipleCalls(t *testing.T) {
|
||||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
ctx := &context.Context{
|
||||
ChatID: "test-chat-id",
|
||||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Space: plan.NewMemorySharedSpace(),
|
||||
}
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
function test(ctx) {
|
||||
try {
|
||||
// Set a value
|
||||
ctx.space.Set("single_use", "use_me_once");
|
||||
|
||||
// First GetDel should work
|
||||
const first = ctx.space.GetDel("single_use");
|
||||
if (first !== "use_me_once") throw new Error("First GetDel failed");
|
||||
|
||||
// Second GetDel should return null (already deleted)
|
||||
const second = ctx.space.GetDel("single_use");
|
||||
if (second !== null && second !== undefined) throw new Error("Second GetDel should return null");
|
||||
|
||||
// Third GetDel should also return null
|
||||
const third = ctx.space.GetDel("single_use");
|
||||
if (third !== null && third !== undefined) throw new Error("Third GetDel should return null");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
first: first,
|
||||
second_is_null: second === null || second === undefined,
|
||||
third_is_null: third === null || third === undefined
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}`, ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
|
||||
result, ok := res.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Expected map result, got %T", res)
|
||||
}
|
||||
|
||||
if !result["success"].(bool) {
|
||||
t.Fatalf("Test failed: %v", result["error"])
|
||||
}
|
||||
|
||||
assert.Equal(t, true, result["success"], "Multiple GetDel calls should work correctly")
|
||||
assert.Equal(t, "use_me_once", result["first"], "First GetDel should return value")
|
||||
assert.Equal(t, true, result["second_is_null"], "Second GetDel should return null")
|
||||
assert.Equal(t, true, result["third_is_null"], "Third GetDel should return null")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue